Completed
Pull Request — master (#192)
by Marek
01:39
created

ige.ospace.IFleet   F

Complexity

Total Complexity 332

Size/Duplication

Total Lines 1387
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1043
dl 0
loc 1387
rs 1.428
c 0
b 0
f 0
wmc 332

32 Methods

Rating   Name   Duplication   Size   Complexity  
A IFleet.surrenderTo() 0 3 1
A IFleet.deleteDesign() 0 4 3
A IFleet.removeFleetName() 0 4 1
A IFleet.setMergeState() 0 6 2
F IFleet.applyShot() 0 98 27
B IFleet.moveAction() 0 16 7
A IFleet.setActionIndex() 0 8 4
C IFleet.applyMine() 0 36 10
B IFleet.distributeExp() 0 21 6
A IFleet.addNewShip() 0 7 1
D IFleet.getScanInfos() 0 48 12
A IFleet.removeShips() 0 10 3
F IFleet.actionDeploy() 0 70 16
A IFleet.init() 0 38 1
F IFleet.processACTIONPhase() 0 127 30
B IFleet.splitFleet() 0 40 7
B IFleet.renameFleet() 0 13 6
C IFleet.deleteAction() 0 19 10
B IFleet.autoRepairAndRecharge() 0 25 8
B IFleet.refuelAndRepairAndRecharge() 0 43 8
A IFleet.clearProcessedActions() 0 11 3
F IFleet.joinFleet() 0 50 17
C IFleet.moveToWormhole() 0 31 10
F IFleet.serviceShips() 0 114 33
C IFleet.disbandFleet() 0 31 10
F IFleet.moveToTarget() 0 108 19
B IFleet.getPreCombatData() 0 45 6
A IFleet.processFINALPhase() 0 6 1
F IFleet.addAction() 0 53 25
F IFleet.update() 0 105 24
F IFleet.actionRedirect() 0 57 15
B IFleet.create() 0 38 6

How to fix   Complexity   

Complexity

Complex classes like ige.ospace.IFleet 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 copy
21
import math
22
import random
23
import re
24
25
import Const
26
import Rules
27
import ShipUtils
28
import Utils
29
30
from ige import GameException, log
31
from ige.IObject import IObject, public
32
from ige.IDataHolder import IDataHolder
33
34
class IFleet(IObject):
35
36
    typeID = Const.T_FLEET
37
38
    def init(self, obj):
39
        IObject.init(self, obj)
40
        #
41
        obj.x = 0.0
42
        obj.y = 0.0
43
        obj.oldX = 0.0
44
        obj.oldY = 0.0
45
        obj.orbiting = Const.OID_NONE
46
        obj.closeSystem = Const.OID_NONE
47
        obj.speed = 0.0
48
        obj.maxSpeed = 0.0
49
        obj.signature = 0
50
        obj.eta = 0.0
51
        obj.target = Const.OID_NONE
52
        #
53
        obj.operEn = 0
54
        obj.storEn = 0
55
        obj.maxEn = 0
56
        obj.operProd = 0.0
57
        obj.ships = []
58
        # action
59
        obj.actions = []
60
        obj.actionIndex = 0
61
        obj.actionWaitCounter = 1
62
        #
63
        obj.speedBoost = 1.0
64
        obj.combatPwr = 0
65
        obj.isMilitary = 0
66
        obj.scannerPwr = 0
67
        obj.origScannerPwr = 0
68
        obj.scannerOn = True
69
        obj.combatExp = 0
70
        obj.combatCounter = 0
71
        obj.combatRetreatWait = 0
72
        obj.lastUpgrade = 0
73
        #
74
        obj.customname = None
75
        obj.allowmerge = 1 #states: 0: no merge; 1: normal merging; 2: fleets can merge with this fleet, but this fleet cannot merge with others
76
77
    def create(self, tran, obj, refObj, owner):
78
        obj.owner = owner
79
        obj.x = refObj.x
80
        obj.y = refObj.y
81
        if refObj.type == Const.T_SYSTEM:
82
            obj.orbiting = refObj.oid
83
            obj.closeSystem = refObj.oid
84
            refObj.fleets.append(obj.oid)
85
            refObj.closeFleets.append(obj.oid)
86
            obj.target = Const.OID_NONE
87
        elif refObj.type == Const.T_FLEET:
88
            obj.oldX = refObj.oldX
89
            obj.oldY = refObj.oldY
90
            obj.orbiting = Const.OID_NONE
91
            obj.closeSystem = refObj.closeSystem
92
            obj.actions = copy.deepcopy(refObj.actions)
93
            obj.actionIndex = refObj.actionIndex
94
            obj.actionWaitCounter = refObj.actionWaitCounter
95
            system = tran.db[obj.closeSystem]
96
            system.closeFleets.append(obj.oid)
97
            obj.target = refObj.target
98
        # collect used names
99
        names = {}
100
        for fleetID in tran.db[owner].fleets:
101
            names[tran.db[fleetID].name] = None
102
        # create name
103
        counter = 1
104
        name = u'Fleet %d' % counter
105
        while True:
106
            name = u'Fleet %d' % counter
107
            counter += 1
108
            if name not in names:
109
                break
110
        obj.name = name
111
        obj.customname = None
112
        obj.allowmerge = 1
113
        # insert fleet into owner's fleets
114
        tran.db[obj.owner].fleets.append(obj.oid)
115
116
    def addNewShip(self, tran, obj, designID):
117
        spec = tran.db[obj.owner].shipDesigns[designID]
118
        obj.ships.append([designID, spec.maxHP, spec.shieldHP, 0])
119
        # new ship has full tanks
120
        obj.storEn += spec.storEn
121
        # update fleet info
122
        self.cmd(obj).update(tran, obj)
123
124
    addNewShip.public = 0
125
126
    @public(Const.AL_OWNER)
127
    def removeShips(self, tran, obj, ships):
128
        for ship in ships:
129
            obj.ships.remove(ship)
130
        if not obj.ships:
131
            log.debug('IFleet', 'removeShips removed last ship')
132
            self.cmd(obj).disbandFleet(tran, obj)
133
        else:
134
            self.cmd(obj).update(tran, obj)
135
        return obj
136
137
    def deleteDesign(self, tran, obj, designID):
138
        # remove design
139
        obj.ships = [ship for ship in obj.ships if ship[0] != designID]
140
        self.cmd(obj).update(tran, obj)
141
142
    deleteDesign.public = 0
143
144
    @public(Const.AL_FULL)
145
    def disbandFleet(self, tran, obj):
146
        log.debug('IFleet', 'disbanding fleet', obj.oid, 'of player', obj.owner)
147
        # remove from player's fleets
148
        try:
149
            if obj.owner != Const.OID_NONE:
150
                tran.db[obj.owner].fleets.remove(obj.oid)
151
        except Exception:
152
            log.warning('IFleet', 'disbandFleet: cannot remove fleet from owner\'s fleet')
153
            pass
154
        # remove from orbit
155
        # remove from index if necessary
156
        if obj.orbiting != Const.OID_NONE:
157
            try:
158
                if tran.db.has_key(obj.orbiting):
159
                    tran.db[obj.orbiting].fleets.remove(obj.oid)
160
            except Exception:
161
                log.warning('IFleet', 'disbandFleet: cannot remove fleet from system.')
162
                pass
163
        # remove from close fleets
164
        if obj.closeSystem != Const.OID_NONE:
165
            try:
166
                if tran.db.has_key(obj.closeSystem):
167
                    tran.db[obj.closeSystem].closeFleets.remove(obj.oid)
168
            except Exception:
169
                log.warning("IFleet", "disbandFleet: cannot remove fleet from the close system.")
170
        # delete from database
171
        try:
172
            tran.db.delete(obj.oid)
173
        except KeyError:
174
            log.warning('IFleet', 'disbandFleet: cannot remove fleet from database.')
175
176
    @public(Const.AL_FULL)
177
    def joinFleet(self, tran, obj, fleetID, force=False):
178
        if obj.orbiting == Const.OID_NONE:
179
            # we are in space
180
            return
181
        if obj.allowmerge != 1:
182
            # owner has turned off auto-joins (join self with other)
183
            return
184
        if fleetID == Const.OID_NONE:
185
            raiseExps = False
186
            # find suitable fleet
187
            system = tran.db[obj.orbiting]
188
            player = tran.db[obj.owner]
189
            for tmpID in system.fleets:
190
                if tmpID == obj.oid:
191
                    continue
192
                fleet = tran.db[tmpID]
193
                if fleet.allowmerge == 0 and not force:
194
                    # owner has turned off auto-joins (join other with self)
195
                    continue
196
                rel = self.cmd(player).getRelationTo(tran, player, fleet.owner)
197
                if rel == Const.REL_UNITY and Utils.isIdleFleet(fleet):
198
                    fleetID = tmpID
199
                    break
200
        else:
201
            raiseExps = True
202
        if fleetID == Const.OID_NONE:
203
            return
204
        # join to selected fleet
205
        fleet = tran.db[fleetID]
206
        # if the fleet was specified from a client call, validate it:
207
        if not fleet.owner == obj.owner:
208
            if raiseExps:
209
                raise GameException("Fleets do not have the same owner.")
210
            return
211
        if not fleet.orbiting == obj.orbiting:
212
            if raiseExps:
213
                raise GameException("Fleets are not in the same system.")
214
            return
215
        if fleet.allowmerge == 0 and not force:
216
            # owner has turned off auto-joins (join other with self)
217
            return
218
        fleet.ships.extend(obj.ships)
219
        # transfer resources
220
        fleet.storEn += obj.storEn
221
        # update fleet's data
222
        self.cmd(fleet).update(tran, fleet)
223
        # disband this fleet
224
        log.debug('IFleet joinFleet, removing old fleet: source fleet',obj.oid,'; target fleet',fleet.oid)
225
        self.cmd(obj).disbandFleet(tran, obj)
226
227
    @public(Const.AL_FULL)
228
    def splitFleet(self, tran, obj, ships, mEn):
229
        if not len(ships):
230
            raise GameException('No ships in the new fleet.')
231
        if len(ships) == len(obj.ships):
232
            raise GameException('No ships in the original fleet.')
233
        # check ships
234
        tmpShips = obj.ships[:]
235
        for ship in ships:
236
            if ship not in tmpShips:
237
                raise GameException("No such ship(s) in the original fleet.")
238
            tmpShips.remove(ship)
239
        # create new fleet
240
        fleet = self.new(Const.T_FLEET)
241
        tran.db.create(fleet)
242
        log.debug(obj.oid, "FLEET -- split fleet, new fleet is", fleet.oid)
243
        if obj.orbiting != Const.OID_NONE:
244
            refObj = tran.db[obj.orbiting]
245
        else:
246
            refObj = obj
247
        self.cmd(fleet).create(tran, fleet, refObj, obj.owner)
248
        # move ships
249
        for ship in ships:
250
            # use server data
251
            idx = obj.ships.index(ship)
252
            ship = obj.ships.pop(idx)
253
            fleet.ships.append(ship)
254
        # update fleet
255
        self.cmd(fleet).update(tran, fleet)
256
        # move en
257
        move = max(min(mEn, fleet.maxEn, obj.storEn), 0)
258
        fleet.storEn += move
259
        obj.storEn -= move
260
        # share speed boost
261
        fleet.speedBoost = obj.speedBoost
262
        # update fleets
263
        self.cmd(obj).update(tran, obj)
264
        self.cmd(fleet).update(tran, fleet)
265
        # return new fleet, old fleet and player's fleets
266
        return fleet, obj, tran.db[obj.owner].fleets
267
268
    @public(Const.AL_FULL)
269
    def renameFleet(self, tran, obj, name):
270
        if not Utils.isCorrectName(name):
271
            raise GameException('Invalid name. Only characters, digits, space, dot and dash permitted, max. length is 30 characters.')
272
        if re.match("/^Fleet \d+$/",name):
273
            raise GameException('Invalid name. You cannot use the format "Fleet ##" for a custom name.')
274
        names = {}
275
        for fleetID in tran.db[obj.owner].fleets:
276
            names[tran.db[fleetID].customname] = None
277
        if name in names and name != obj.customname:
278
            raise GameException('Name already in use.')
279
        obj.customname = name
280
        return obj.customname
281
282
    @public(Const.AL_FULL)
283
    def removeFleetName(self, tran, obj):
284
        obj.customname = None
285
        return obj.name
286
287
    @public(Const.AL_FULL)
288
    def setMergeState(self, tran, obj, state):
289
        if not state in [0,1,2]:
290
            raise GameException('Bad join fleet state.') #should we log this? Probably don't need to.
291
        obj.allowmerge = state
292
        return obj.allowmerge
293
294
    def update(self, tran, obj):
295
        # if there are no ships -> disband fleet
296
        if not len(obj.ships) or obj.owner == Const.OID_NONE:
297
            log.warning(obj.oid, "FLEET - no ships in the fleet -- disbanding")
298
            self.cmd(obj).disbandFleet(tran, obj)
299
            return
300
        obj.origScannerPwr = 0
301
        obj.operEn = 0
302
        obj.operProd = 0.0
303
        obj.maxEn = 0
304
        obj.maxSpeed = 999999.9
305
        obj.combatPwr = 0
306
        obj.isMilitary = 0
307
        #ships = {}
308
        # find
309
        player = tran.db.get(obj.owner, None)
310
        if not player or player.type not in Const.PLAYER_TYPES or obj.oid not in player.fleets:
311
            # disband fleet when owner is invalid
312
            log.warning(obj.oid, "Disbanding fleet - invalid owner", obj)
313
            self.cmd(obj).disbandFleet(tran, obj)
314
            return
315
        obj.signature = 0
316
        remove = []
317
        idx = 0
318
        for designID, hp, shield, exp in obj.ships:
319
            if designID in player.shipDesigns:
320
                tech = player.shipDesigns[designID]
321
                obj.origScannerPwr = max(tech.scannerPwr, obj.origScannerPwr)
322
                obj.operEn += tech.operEn
323
                obj.operProd += tech.buildProd * Rules.operProdRatio
324
                obj.maxEn += tech.storEn
325
                obj.maxSpeed = min(obj.maxSpeed, tech.speed)
326
                obj.signature += tech.signature
327
                obj.combatPwr += int(tech.combatPwr * float(hp + shield) / (tech.maxHP + tech.shieldHP))
328
                obj.isMilitary = obj.isMilitary or tech.isMilitary
329
                #ships[tech.signature] = ships.get(tech.signature, 0) + 1
330
                if obj.ships[idx][1] > tech.maxHP:
331
                    log.debug(obj.oid, "Too high maxHP for ship, player", obj.owner)
332
                    obj.ships[idx][1] = min(obj.ships[idx][1], tech.maxHP)
333
            else:
334
                # TODO track this problem
335
                log.warning("Player has not this designID", player.oid, designID)
336
                remove.append([designID, hp, shield, exp])
337
            idx += 1
338
        # delete ships intended for removal
339
        for shipSpec in remove:
340
            obj.ships.remove(shipSpec)
341
        # misc
342
        obj.signature = min(obj.signature, Rules.maxSignature)
343
        obj.signature = max(obj.signature,1) #require fleet signature to be at least 1 now that we removed that from a per-ship basis
344
        obj.speed = obj.maxSpeed
345
        # storage
346
        obj.storEn = min(obj.storEn, obj.maxEn)
347
        # sort ships only when there is no combat
348
        # this prevents resorting fleets in combat
349
        if obj.combatCounter == 0:
350
            obj.ships = ShipUtils.sortShips(obj.ships)
351
        else:
352
            log.debug("Skipping ship (re)sorting [fleet in combat]", obj.oid)
353
        # closest system
354
        if not tran.db.has_key(obj.closeSystem) or tran.db[obj.closeSystem].type not in (Const.T_SYSTEM, Const.T_WORMHOLE):
355
            if obj.orbiting == Const.OID_NONE:
356
                log.debug("No close system for fleet", obj.oid)
357
                # select any system
358
                systemID = tran.db[tran.db[Const.OID_UNIVERSE].galaxies[0]].systems[0]
359
                obj.closeSystem = systemID
360
                log.debug(obj.oid, "Setting NULL close system to", systemID)
361
            else:
362
                log.debug(obj.oid, "Generating close system from orbiting", obj.orbiting)
363
                obj.closeSystem = obj.orbiting
364
            system = tran.db[obj.closeSystem]
365
            if obj.oid not in system.closeFleets:
366
                system.closeFleets.append(obj.oid)
367
        # verify close system
368
        if tran.db.has_key(obj.closeSystem):
369
            system = tran.db[obj.closeSystem]
370
            if system.type in (Const.T_SYSTEM, Const.T_WORMHOLE):
371
                if obj.oid not in system.closeFleets:
372
                    log.debug("Adding fleet", obj.oid, "into closeFleets of", system.oid)
373
                    system.closeFleets.append(obj.oid)
374
            else:
375
                log.debug(obj.oid, "Close system is not a system")
376
                obj.closeSystem = Const.OID_NONE
377
        else:
378
            log.debug(obj.oid, "Close system does not exists")
379
            obj.closeSystem = Const.OID_NONE
380
        # compute scanner pwr
381
        if obj.closeSystem:
382
                system = tran.db[obj.closeSystem]
383
                emrLevel = tran.db[system.compOf].emrLevel
384
                obj.scannerPwr = min(int(obj.origScannerPwr * (2.0 - emrLevel)), Rules.scannerMaxPwr)
385
        # replace obsolete commands
386
        for actionTuple in obj.actions[:]:
387
            try:
388
                action, target, actionData = actionTuple
389
            except:
390
                log.warning(obj.oid, "Removing action", actionTuple)
391
                obj.actions.remove(actionTuple)
392
        index = 0
393
        for action, target, actionData in obj.actions:
394
            if action == Const.FLACTION_DEPLOY and actionData not in player.shipDesigns:
395
                # deployment of scrapped ship
396
                log.debug(obj.oid, "invalid ship to deploy")
397
                obj.actions[index] = (Const.FLACTION_NONE, None, None)
398
            index += 1
399
400
    update.public = 0
401
402
    def getScanInfos(self, tran, obj, scanPwr, player):
403
        if obj.owner == player.oid:
404
            return []
405
        if scanPwr >= Rules.level1InfoScanPwr:
406
            result = IDataHolder()
407
            result._type = Const.T_SCAN
408
            result.scanPwr = scanPwr
409
            result.oid = obj.oid
410
            result.x = obj.x
411
            result.y = obj.y
412
            result.oldX = obj.oldX
413
            result.oldY = obj.oldY
414
            result.eta = obj.eta
415
            result.signature = obj.signature
416
            result.type = obj.type
417
            result.orbiting = obj.orbiting
418
            if obj.orbiting == Const.OID_NONE and obj.actionIndex < len(obj.actions):
419
                target = obj.actions[obj.actionIndex][1]
420
                targetObj = tran.db[target]
421
                if targetObj.type == Const.T_PLANET:
422
                    result.target = targetObj.compOf
423
                else:
424
                    result.target = target
425
        else:
426
            return []
427
        if scanPwr >= Rules.level2InfoScanPwr:
428
            result.owner = obj.owner
429
            if obj.customname:
430
                result.name = obj.customname
431
            else:
432
                result.name = obj.name
433
        if scanPwr >= Rules.level3InfoScanPwr:
434
            result.isMilitary = obj.isMilitary
435
            result.combatPwr = obj.combatPwr
436
        if scanPwr >= Rules.level4InfoScanPwr:
437
            # provide less information
438
            result.shipScan = {}
439
            owner = tran.db[obj.owner]
440
            for designID, hp, shield, exp in obj.ships:
441
                tech = owner.shipDesigns[designID]
442
                key = tech.name, tech.combatClass, tech.isMilitary
443
                result.shipScan[key] = result.shipScan.get(key, 0) + 1
444
        if scanPwr >= Rules.partnerScanPwr:
445
            result.scannerPwr = obj.scannerPwr
446
            result.allowmerge = obj.allowmerge
447
            result.customname = obj.customname
448
            result.name = obj.name
449
        return [result]
450
451
    @public(Const.AL_FULL)
452
    def addAction(self, tran, obj, index, action, targetID, aData):
453
        # check if target is valid
454
        if action == Const.FLACTION_REDIRECT:
455
            if targetID != Const.OID_NONE:
456
                raise GameException("This command has no target.")
457
        elif action == Const.FLACTION_WAIT or action == Const.FLACTION_REPEATFROM:
458
            if targetID != Const.OID_NONE:
459
                raise GameException("This command has no target.")
460
            aData = int(aData)
461
            if aData < 0:
462
                raise GameException("Number equal or larger than 1 must be specified.")
463
        elif action == Const.FLACTION_DECLAREWAR:
464
            if targetID != Const.OID_NONE:
465
                raise GameException("This command has no target.")
466
            if aData == Const.OID_NONE or aData == obj.owner:
467
                raise GameException("Invalid commander.")
468
        else:
469
            target = tran.db[targetID]
470
            if target.type not in (Const.T_SYSTEM, Const.T_WORMHOLE, Const.T_PLANET):
471
                raise GameException('Can target wormholes, systems or planets only.')
472
            if action == Const.FLACTION_ENTERWORMHOLE and target.type != Const.T_WORMHOLE:
473
                raise GameException('Can only traverse wormholes.')
474
            if action == Const.FLACTION_DEPLOY and target.type != Const.T_PLANET:
475
                raise GameException('Can build on/colonize planets only.')
476
            if len(obj.actions) + 1 > Rules.maxCmdQueueLen:
477
                raise GameException('Too many commands in the queue.')
478
            #validate that the target is in the fleet owner's galaxy
479
            if target.type == Const.T_PLANET:
480
                systemID = target.compOf
481
            else:
482
                systemID = targetID
483
            owner = tran.db[obj.owner]
484
            targetSystem = tran.db[systemID]
485
            # validate that the player has actually scanned this system
486
            if systemID not in owner.validSystems:
487
                raise GameException('You cannot find this system (never scanned).')
488
            if not owner.galaxy:
489
                raise GameException('The fleet owner is not in a galaxy.')
490
            galaxy = tran.db[owner.galaxy]
491
            if systemID not in galaxy.systems:
492
                raise GameException('The target system is not in your galaxy.')
493
            # because of universal visibility, fleets cannot target black holes
494
            # it would make exploration too easy
495
            if targetSystem.starClass[0] == 'b':
496
                raise GameException('It would be too dangerous to go there.')
497
498
        obj.actions.insert(index, (action, targetID, aData))
499
        if index <= obj.actionIndex:
500
            obj.actionIndex += 1
501
        if obj.actionIndex >= len(obj.actions) or obj.actionIndex < 0:
502
            obj.actionIndex = min(index, len(obj.actions) - 1)
503
        return obj.actions, obj.actionIndex
504
505
    @public(Const.AL_FULL)
506
    def deleteAction(self, tran, obj, index):
507
        if index >= len(obj.actions) or index < 0:
508
            raise GameException('Index out of bounds.')
509
        if index == obj.actionIndex and obj.orbiting == Const.OID_NONE:
510
            if obj.actions[index][0] == Const.FLACTION_MOVE:
511
                raise GameException('Move command in progress cannot be deleted.')
512
            else:
513
                # convert action to the move command
514
                action, targetID, aData = obj.actions[index]
515
                obj.actions[index] = (Const.FLACTION_MOVE, targetID, aData)
516
                return obj.actions, obj.actionIndex
517
        if index == obj.actionIndex and obj.actions[index][0] == Const.FLACTION_WAIT:
518
            # reset wait counters
519
            obj.actionWaitCounter = 1
520
        del obj.actions[index]
521
        if index <= obj.actionIndex and obj.actionIndex > 0:
522
            obj.actionIndex -= 1
523
        return obj.actions, obj.actionIndex
524
525
    @public(Const.AL_FULL)
526
    def setActionIndex(self, tran, obj, index):
527
        if index >= len(obj.actions) or index < 0:
528
            raise GameException('Index out of bounds.')
529
        if obj.orbiting == Const.OID_NONE:
530
            raise GameException('Move command in progress cannot be changed.')
531
        obj.actionIndex = index
532
        return obj.actionIndex
533
534
    @public(Const.AL_FULL)
535
    def moveAction(self, tran, fleet, index, rel):
536
        if index >= len(fleet.actions):
537
            raise GameException('No such item in the command list.')
538
        if index + rel < 0 or index + rel >= len(fleet.actions):
539
            raise GameException('Cannot move.')
540
        if index == fleet.actionIndex:
541
            raise GameException('Cannot move active command.')
542
        if index < fleet.actionIndex:
543
            raise GameException('Cannot move processed command.')
544
        if index + rel <= fleet.actionIndex:
545
            raise GameException('Cannot move before active command.')
546
        action = fleet.actions[index]
547
        del fleet.actions[index]
548
        fleet.actions.insert(index + rel, action)
549
        return fleet.actions
550
551
    @public(Const.AL_FULL)
552
    def clearProcessedActions(self, tran, fleet):
553
        if fleet.actionIndex <= 0:
554
            return (fleet.actions, fleet.actionIndex)
555
556
        for actionIdx in range(0, fleet.actionIndex):
557
            del fleet.actions[0]
558
559
        fleet.actionIndex = 0
560
561
        return (fleet.actions, fleet.actionIndex)
562
563
    @public(Const.AL_ADMIN)
564
    def processACTIONPhase(self, tran, obj, data):
565
        #@log.debug("Fleet", obj.oid, "ACTION")
566
        # update fleet data
567
        self.cmd(obj).update(tran, obj)
568
        # consume support
569
        if obj.storEn >= obj.operEn:
570
            obj.storEn -= obj.operEn
571
            # refuel
572
            refuelled = self.cmd(obj).refuelAndRepairAndRecharge(tran, obj)
573
        else:
574
            # try to refuel fleet
575
            refuelled = self.cmd(obj).refuelAndRepairAndRecharge(tran, obj)
576
            # there is not enought support -> damage ships
577
            log.debug('IFleet', 'No support - damaging ships in fleet', obj.oid)
578
            index = 0
579
            player = tran.db[obj.owner]
580
            destroyed = []
581
            for designID, hp, shield, exp in obj.ships:
582
                spec = player.shipDesigns[designID]
583
                operEn = spec.operEn
584
                if obj.storEn >= spec.operEn:
585
                    #@log.debug('IFleet', 'Ship SUPPORT OK', shipTechID)
586
                    obj.storEn -= spec.operEn
587
                elif obj.storEn > 0:
588
                    # consume remaining fuel
589
                    obj.storEn = 0
590
                else:
591
                    # apply damage
592
                    dmg = max(int(spec.maxHP * Rules.shipDecayRatio), 1)
593
                    if dmg >= hp:
594
                        destroyed.append(obj.ships[index])
595
                    else:
596
                        obj.ships[index][Const.SHIP_IDX_HP] -= dmg
597
                index += 1
598
            self.cmd(obj).removeShips(tran, obj, destroyed)
599
            # if fleet has been destroyed -> abort action processing and send message
600
            if not tran.db.has_key(obj.oid):
601
                if obj.orbiting:
602
                    system = tran.db[obj.orbiting]
603
                    Utils.sendMessage(tran, player, Const.MSG_FUEL_LOST_ORBITING, system.oid, (obj.name, system.oid))
604
                else:
605
                    action, target, actionData = obj.actions[obj.actionIndex]
606
                    Utils.sendMessage(tran, player, Const.MSG_FUEL_LOST_FLYING, target, (obj.name, target))
607
                log.debug('IFleet', obj.oid, 'fleet destroyed')
608
                return
609
        # upgrade ships
610
        if obj.orbiting != Const.OID_NONE:
611
            # autoRepair is part of serviceShips
612
            self.cmd(obj).serviceShips(tran, obj)
613
            # record scanner into system scanner overview
614
            system = tran.db[obj.orbiting]
615
            system.scannerPwrs[obj.owner] = max(obj.scannerPwr, system.scannerPwrs.get(obj.owner, 0))
616
        # ACTIONS
617
        if Utils.isIdleFleet(obj):
618
            #@log.debug('IFleet', obj.oid, 'fleet idle')
619
            # reset retreat counter
620
            obj.combatRetreatWait = 0
621
            # reset last position to current position
622
            obj.oldX = obj.x
623
            obj.oldY = obj.y
624
            # there is nothing to do - try to join other fleets
625
            self.cmd(obj).joinFleet(tran, obj, Const.OID_NONE)
626
            return
627
        #@log.debug('IFleet', obj.oid, 'processing action', action)
628
        while not Utils.isIdleFleet(obj):
629
            action, target, actionData = obj.actions[obj.actionIndex]
630
            if action == Const.FLACTION_NONE:
631
                obj.actionIndex += 1
632
            elif action == Const.FLACTION_DEPLOY:
633
                if self.cmd(obj).actionDeploy(tran, obj):
634
                    obj.actionIndex += 1
635
                break
636
            elif action == Const.FLACTION_WAIT:
637
                if obj.actionWaitCounter >= actionData:
638
                    obj.actionWaitCounter = 1
639
                    obj.actionIndex += 1
640
                else:
641
                    obj.actionWaitCounter += 1
642
                break #wait should wait, not let move; deindented this to act for completed waits also --RC
643
            elif action == Const.FLACTION_MOVE:
644
                if self.cmd(obj).moveToTarget(tran, obj, target):
645
                    # we are there
646
                    obj.actionIndex += 1
647
                break
648
            elif action == Const.FLACTION_ENTERWORMHOLE:
649
                if self.cmd(obj).moveToWormhole(tran, obj, target):
650
                    # we are there
651
                    obj.actionIndex += 1
652
                break
653
            elif action == Const.FLACTION_DECLAREWAR:
654
                # switch off pact allow military ships
655
                player = tran.db[obj.owner]
656
                self.cmd(player).changePactCond(tran, player, actionData,
657
                    Const.PACT_ALLOW_MILITARY_SHIPS, Const.PACT_OFF, [Const.PACT_ALLOW_MILITARY_SHIPS])
658
                # next action
659
                obj.actionIndex +=1
660
            elif action == Const.FLACTION_REFUEL:
661
                # check current refuel level
662
                if self.cmd(obj).moveToTarget(tran, obj, target) and refuelled:
663
                    # next action
664
                    obj.actionIndex += 1
665
                else:
666
                    break
667
            elif action == Const.FLACTION_REDIRECT:
668
                # ok, let's do some magic
669
                if self.cmd(obj).actionRedirect(tran, obj, refuelled):
670
                    obj.actionIndex += 1
671
                else:
672
                    break
673
            elif action == Const.FLACTION_REPEATFROM:
674
                log.debug(obj.oid, "Setting action index to", data)
675
                if actionData != None:
676
                    obj.actionIndex = actionData
677
                else:
678
                    obj.actionIndex += 1
679
                break # TODO fix me
680
            else:
681
                raise GameException('Unsupported action %d' % action)
682
                break
683
        # it there is nothing to do -> join other idle fleets
684
        # the fleet could joined with another fleet
685
        if tran.db.has_key(obj.oid) and Utils.isIdleFleet(obj):
686
            # reset retreat counter
687
            obj.combatRetreatWait = 0
688
            # try to join some fleet
689
            self.cmd(obj).joinFleet(tran, obj, Const.OID_NONE)
690
691
    def actionRedirect(self, tran, obj, refuelled):
692
        if obj.orbiting != Const.OID_NONE:
693
            # try to find fleet with the redirect command (<10 ships)
694
            # and join it
695
            system = tran.db[obj.orbiting]
696
            for fleetID in system.fleets:
697
                fleet = tran.db[fleetID]
698
                if fleet.owner != obj.owner or obj.oid == fleetID:
699
                    continue
700
                if Utils.isIdleFleet(fleet):
701
                    continue
702
                action, target, actionData = fleet.actions[fleet.actionIndex]
703
                # same command, less than 20 ships in the resulting fleet
704
                if action == Const.FLACTION_REDIRECT and len(fleet.ships) + len(obj.ships) <= 20:
705
                    # join it
706
                    log.debug("JOINING", obj.oid, fleetID)
707
                    self.cmd(obj).joinFleet(tran, obj, fleetID)
708
                    # "join" targets
709
                    fleet.actions[fleet.actionIndex] = (
710
                        action,
711
                        max(obj.actions[obj.actionIndex][1], target),
712
                        actionData,
713
                    )
714
                    return 0
715
        # move?
716
        action, target, actionData = obj.actions[obj.actionIndex]
717
        if obj.orbiting == Const.OID_NONE or target != Const.OID_NONE:
718
            # ok, the target was already selected
719
            if not self.cmd(obj).moveToTarget(tran, obj, target):
720
                # keep moving
721
                return 0
722
        # we are in the system - delete target
723
        obj.actions[obj.actionIndex] = (action, Const.OID_NONE, actionData)
724
        # check if current system has a redirection
725
        player = tran.db[obj.owner]
726
        if obj.orbiting not in player.shipRedirections:
727
            # there is no redirection, we are done
728
            return 1
729
        # select a new target if tanks are full
730
        # departure every 6th turn
731
        turn = tran.db[Const.OID_UNIVERSE].turn
732
        if refuelled and turn % 6 == 0:
733
            obj.actions[obj.actionIndex] = (action, player.shipRedirections[obj.orbiting], actionData)
734
        return 0
735
736
        # old code
737
        # check if current system has any redirection
738
        player = tran.db[obj.owner]
739
        if obj.orbiting not in player.shipRedirections:
740
            return 1
741
        # form new command queue
742
        obj.actions = [
743
            [Const.FLACTION_REFUEL, player.shipRedirections[obj.orbiting], None],
744
            [Const.FLACTION_REDIRECT, Const.OID_NONE, None],
745
        ]
746
        obj.actionIndex = 0
747
        return 0
748
749
    actionRedirect.public = 0
750
751
    def actionDeploy(self, tran, obj):
752
        action, target, actionData = obj.actions[obj.actionIndex]
753
        if not self.cmd(obj).moveToTarget(tran, obj, target):
754
            return 0
755
        # deploy ship
756
        log.debug('IFleet', 'Deploying on planet - tech', actionData)
757
        planet = tran.db[target]
758
        player = tran.db[obj.owner]
759
        # find ship containing specified building
760
        for designID, hp, shield, exp in obj.ships:
761
            tech = player.shipDesigns[designID]
762
            if designID == actionData:
763
                removeShip = 0
764
                for deployHandlerID in tech.deployHandlers: #do handlers first so that structures can deploy on new planets
765
                    if not (type(deployHandlerID) in (str,int,long)): #just a double check...
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable long does not seem to be defined.
Loading history...
766
                        continue
767
                    if not deployHandlerID.isdigit():
768
                        continue
769
                    log.debug('IFleet -', 'Attempting deploy of',deployHandlerID)
770
                    try:
771
                        deployHandlerID = int(deployHandlerID) #just a double check...
772
                    except:
773
                        log.warning('IFleet -','Deployment failed: NAN')
774
                        continue
775
                    deployHandler = Rules.techs[deployHandlerID]
776
                    if deployHandler.deployHandlerValidator(tran, obj, planet, deployHandler):
777
                        try:
778
                            deployHandler.deployHandlerFunction(tran, obj, planet, deployHandler)
779
                            Utils.sendMessage(tran, obj, Const.MSG_DEPLOY_HANDLER, planet.oid, deployHandlerID)
780
                            removeShip = 1
781
                        except GameException, e:
782
                            log.warning('IFleet -','Deploy handler error - internal error')
783
                            Utils.sendMessage(tran, obj, Const.MSG_CANNOTBUILD_SHLOST, planet.oid, None)
784
                    else:
785
                        log.debug('IFleet -', 'Deploy handler - validation failed')
786
                        Utils.sendMessage(tran, obj, Const.MSG_CANNOTBUILD_SHLOST, planet.oid, None)
787
788
                for structTechID in tech.deployStructs:
789
                    if not (type(structTechID) in (int,long)): #just a double check...
790
                        continue
791
                    structTech = Rules.techs[structTechID]
792
                    # validate
793
                    if structTech.validateConstrHandler(tran, obj, planet, structTech):
794
                        # build it
795
                        if len(planet.slots) < planet.plSlots:
796
                            try:
797
                                structTech.finishConstrHandler(tran, obj, planet, structTech)
798
                                planet.slots.insert(0, Utils.newStructure(tran, structTechID, obj.owner, hpRatio = Rules.structFromShipHpRatio))
799
                                removeShip = 1
800
                                Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_STRUCTURE, planet.oid, structTech.id)
801
                            except GameException, e:
802
                                # cannot build (planet already occupied?)
803
                                log.warning('IFleet -', 'Build on planet - cannot complete')
804
                                Utils.sendMessage(tran, obj, Const.MSG_CANNOTBUILD_SHLOST, planet.oid, None)
805
                        else:
806
                            # no free slot
807
                            log.debug('IFleet -', 'Build on planet - no free slot')
808
                            Utils.sendMessage(tran, obj, Const.MSG_CANNOTBUILD_NOSLOT, planet.oid, None)
809
                    else:
810
                        # cannot build this here TODO report it
811
                        log.debug('IFleet -', 'Build on planet - cannot build here (validation)')
812
                if removeShip:
813
                    self.cmd(obj).removeShips(tran, obj, [[designID, hp, shield, exp]])
814
                    # ship has been deployed
815
                    return 1
816
        # no suitable ship in fleet TODO report it
817
        log.debug('IFleet -', 'Deploy ship - no suitable ship')
818
        return 1
819
820
        actionDeploy.public = 0
821
822
    def refuelAndRepairAndRecharge(self, tran, obj):
823
        if obj.orbiting == Const.OID_NONE:
824
            # we are in space
825
            return 0
826
        # find ALLIED PLANETS
827
        system = tran.db[obj.orbiting]
828
        player = tran.db[obj.owner]
829
        refuelMax = 0
830
        refuelInc = 0
831
        repairShip = 0.0
832
        for planetID in system.planets:
833
            planet = tran.db[planetID]
834
            if planet.owner == Const.OID_NONE:
835
                continue
836
            if planet.owner == player.oid:
837
                refuelMax = max(refuelMax, planet.refuelMax)
838
                refuelInc = max(refuelInc, planet.refuelInc)
839
                repairShip = max(repairShip, planet.repairShip)
840
            elif self.cmd(player).isPactActive(tran, player, planet.owner, Const.PACT_ALLOW_TANKING):
841
                refuelMax = max(refuelMax, planet.refuelMax)
842
                refuelInc = max(refuelInc, planet.refuelInc)
843
                repairShip = max(repairShip, planet.repairShip)
844
        # repair ships
845
        self.cmd(obj).autoRepairAndRecharge(tran, obj, forceRepairPerc = repairShip)
846
        # tank
847
        if refuelMax == 0:
848
            return 1
849
        currentLevel = int(100.0 * obj.storEn / obj.maxEn)
850
        #@log.debug(obj.oid, "Refuel", currentLevel, refuelMax)
851
        if currentLevel >= refuelMax:
852
            # don't burn any fuel if you can refuel
853
            obj.storEn = min(obj.maxEn, obj.storEn + obj.operEn)
854
            return 1
855
        obj.storEn = min(
856
            int(math.ceil(obj.maxEn * refuelInc / 100.0 + obj.operEn + obj.storEn)),
857
            int(math.ceil(obj.maxEn * refuelMax / 100.0)),
858
            obj.maxEn,
859
        )
860
        #@log.debug("Refuelling", obj.oid, refuelInc, refuelMax)
861
        currentLevel = 100.0 * obj.storEn / obj.maxEn
862
        #@log.debug(obj.oid, "After refuel", currentLevel, refuelMax)
863
        #@log.debug(obj.oid, "Tanks after refuel", obj.storEn, "/", obj.maxEn)
864
        return currentLevel >= refuelMax
865
866
    refuelAndRepairAndRecharge.public = 0
867
868
    def serviceShips(self, tran, obj):
869
        player = tran.db[obj.owner]
870
        # check conditions
871
        # no combat in the system
872
        system = tran.db[obj.orbiting]
873
        if system.combatCounter != 0:
874
            return
875
        # player's or ally's planet in the system and upgrade facility there
876
        # check for train facilities too
877
        upgrPlanets = []
878
        trainPlanets = []
879
        trainShipInc = 0.0
880
        trainShipMax = 0
881
        for planetID in system.planets:
882
            planet = tran.db[planetID]
883
            if planet.owner == player.oid and planet.upgradeShip > 0:
884
                upgrPlanets.append(planet)
885
            elif self.cmd(player).isPactActive(tran, player, planet.owner, Const.PACT_ALLOW_TANKING) and planet.upgradeShip > 0:
886
                upgrPlanets.append(planet)
887
            if planet.owner == player.oid and planet.trainShipInc > 0.0:
888
                trainShipInc = max(trainShipInc, planet.trainShipInc)
889
                trainShipMax = max(trainShipMax, planet.trainShipMax)
890
        # train ships
891
        if trainShipInc > 0:
892
            for index, ship in enumerate(obj.ships):
893
                spec = player.shipDesigns[ship[Const.SHIP_IDX_DESIGNID]]
894
                if ship[Const.SHIP_IDX_EXP] / spec.baseExp < trainShipMax and spec.isMilitary:
895
                    ship[Const.SHIP_IDX_EXP] = min(
896
                        spec.baseExp * trainShipMax,
897
                        ship[Const.SHIP_IDX_EXP] + max(int(trainShipInc * spec.baseExp), 1),
898
                    )
899
        if not upgrPlanets:
900
            # no service facility
901
            return
902
        upgraded = 0
903
        # perform upgrade
904
        for designID in player.shipDesigns.keys():
905
            spec = player.shipDesigns[designID]
906
            if spec.upgradeTo:
907
                #@log.debug("Upgrading design", designID, "to", spec.upgradeTo, "for player", player.oid)
908
                upgradeToSpec = player.shipDesigns[spec.upgradeTo]
909
                player.fleetUpgradeInProgress = 1
910
                diff = max(
911
                    Rules.shipMinUpgrade,
912
                    int((upgradeToSpec.buildProd - spec.buildProd) * Rules.shipUpgradeMod),
913
                )
914
                if player.fleetUpgradePool < diff:
915
                    continue
916
                # scan all ships for design
917
                designExists = 0
918
                for index in xrange(0, len(obj.ships)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
919
                    if obj.ships[index][Const.SHIP_IDX_DESIGNID] == designID:
920
                        # find planet with free upgrade points
921
                        needsUPts = Rules.shipUpgradePts[upgradeToSpec.combatClass]
922
                        planet = None
923
                        for tmpPlanet in upgrPlanets:
924
                            if tmpPlanet.upgradeShip >= needsUPts:
925
                                planet = tmpPlanet
926
                                break
927
                        if not planet:
928
                            break
929
                        # check strategic resources
930
                        neededSR = {}
931
                        # new design
932
                        for sr in upgradeToSpec.buildSRes:
933
                            if not sr in neededSR:
934
                                neededSR[sr] = 0
935
                            neededSR[sr] += upgradeToSpec.buildSRes[sr]
936
                        # old design
937
                        for sr in spec.buildSRes:
938
                            if not sr in neededSR:
939
                                neededSR[sr] = 0
940
                            neededSR[sr] -= spec.buildSRes[sr]
941
                        # check player's resources
942
                        ok = 1
943
                        for sr in neededSR:
944
                            if player.stratRes.get(sr, 0) < neededSR[sr]:
945
                                Utils.sendMessage(tran, obj, Const.MSG_CANNOT_UPGRADE_SR, obj.oid, (spec.name, upgradeToSpec.name, sr))
946
                                # skip this ship
947
                                ok = 0
948
                        if not ok:
949
                            # skip this ship
950
                            break
951
                        # consume strategic resources
952
                        for sr in neededSR:
953
                            player.stratRes[sr] -= neededSR[sr]
954
                        # upgrade ship
955
                        log.debug("Upgrading ship in fleet", obj.oid, needsUPts, planet.upgradeShip, planet.oid)
956
                        maxHPRatio = max(0.01, 1.0 - max(upgradeToSpec.buildProd - spec.buildProd, 0) / float(upgradeToSpec.buildProd))
957
                        obj.ships[index][Const.SHIP_IDX_DESIGNID] = spec.upgradeTo
958
                        obj.ships[index][Const.SHIP_IDX_HP] = max(1, min(
959
                            obj.ships[index][1],
960
                            int(upgradeToSpec.maxHP * maxHPRatio)
961
                        ))
962
                        obj.ships[index][Const.SHIP_IDX_SHIELDHP] = upgradeToSpec.shieldHP
963
                        # cap max experience based on equivilent percentage of experience transfer (prevent high baseExp ship upgrading to low baseExp ships with a higher bonus)
964
                        obj.ships[index][Const.SHIP_IDX_EXP] = min(obj.ships[index][Const.SHIP_IDX_EXP],int(1.0 * upgradeToSpec.baseExp / spec.baseExp * obj.ships[index][Const.SHIP_IDX_EXP]))
965
                        upgraded += 1
966
                        #@log.debug("HP penalty", diff, upgradeToSpec.buildProd, maxHPRatio)
967
                        player.fleetUpgradePool -= diff
968
                        designExists = 1
969
                        # consume upgrade points
970
                        planet.upgradeShip -= needsUPts
971
                        # record last upgrade
972
                        obj.lastUpgrade = tran.db[Const.OID_UNIVERSE].turn
973
                        # send a message to the player
974
                        # Utils.sendMessage(tran, obj, Const.MSG_UPGRADED_SHIP, obj.oid, (spec.name, player.shipDesigns[spec.upgradeTo].name))
975
                        if player.fleetUpgradePool < diff:
976
                            break
977
                if player.fleetUpgradePool < diff:
978
                    break
979
        # fix fleet stats
980
        if upgraded > 0:
981
            self.cmd(obj).update(tran, obj)
982
983
    serviceShips.public = 0
984
985
    def autoRepairAndRecharge(self, tran, obj, forceRepairPerc = 0.0):
986
        player = tran.db[obj.owner]
987
        idx = 0
988
        for designID, hp, shields, exp in obj.ships:
989
            spec = player.shipDesigns[designID]
990
            if hp < spec.maxHP:
991
                if obj.storEn == 0:
992
                    repairFix = 0
993
                    repairPerc = forceRepairPerc
994
                else:
995
                    repairFix = spec.autoRepairFix
996
                    repairPerc = max(spec.autoRepairPerc, forceRepairPerc)
997
                if repairFix > 0 or repairPerc > 0:
998
                    #&log.debug("IFleet - repairing ship", obj.oid, designID, hp, repairFix, repairPerc)
999
                    obj.ships[idx][Const.SHIP_IDX_HP] = int(min(
1000
                        spec.maxHP,
1001
                        hp + repairFix + max(1, spec.maxHP * repairPerc),
1002
                    ))
1003
            if shields < spec.shieldHP and obj.storEn:
1004
                #@log.debug("IFleet - recharging shields", designID, shields, spec.shieldRechargeFix, spec.shieldRechargePerc)
1005
                obj.ships[idx][Const.SHIP_IDX_SHIELDHP] = int(min(
1006
                    spec.shieldHP,
1007
                    shields + spec.shieldRechargeFix + max(1, spec.shieldHP * spec.shieldRechargePerc),
1008
                ))
1009
            idx += 1
1010
1011
    autoRepairAndRecharge.public = 0
1012
1013
    def moveToWormhole(self, tran, obj, targetID):
1014
        origin = tran.db[targetID]
1015
        if not (obj.x==origin.x and obj.y==origin.y):
1016
            if not self.cmd(obj).moveToTarget(tran, obj, targetID):
1017
                return 0 #ship hasn't arrived
1018
        # enter wormhole
1019
        if origin.type == Const.T_WORMHOLE: #is wormhole, now enter it!
1020
            destinationWormHole = tran.db[origin.destinationOid]
1021
            if destinationWormHole.oid == targetID:
1022
                return 1
1023
            if obj.oid not in destinationWormHole.fleets:
1024
                destinationWormHole.fleets.append(obj.oid)
1025
            if obj.oid not in destinationWormHole.closeFleets:
1026
                destinationWormHole.closeFleets.append(obj.oid)
1027
            if obj.oid in origin.fleets:
1028
                origin.fleets.remove(obj.oid)
1029
            if obj.oid in origin.closeFleets:
1030
                origin.closeFleets.remove(obj.oid)
1031
            obj.closeSystem = destinationWormHole.oid
1032
            log.debug('IFleet', 'Entering Wormhole - destination ', destinationWormHole.oid)
1033
            obj.orbiting = destinationWormHole.oid
1034
            obj.x = destinationWormHole.x
1035
            obj.y = destinationWormHole.y
1036
            destinationWormHole.scannerPwrs[obj.owner] = max(obj.scannerPwr, destinationWormHole.scannerPwrs.get(obj.owner, 0))
1037
            Utils.sendMessage(tran, obj, Const.MSG_ENTERED_WORMHOLE, destinationWormHole.oid , (origin.name,destinationWormHole.name))
1038
            arrived = 1
1039
        else: #is not wormhole...how'd you ever execute this command? Or is there some weird "terraform wormhole" technology we never forsaw?
1040
            log.warning('IFleet', 'Cannot enter non-existant wormhole at location ', origin.oid)
1041
            #Utils.sendMessage(tran, obj, Const.MSG_ENTERED_WORMHOLE, destinationWormHole.oid , (origin.name,destinationWormHole.name))
1042
            arrived = 1 #since the move part was successful, just ignore this problem for the player
1043
        return arrived
1044
1045
    moveToWormhole.public = 0
1046
1047
    def moveToTarget(self, tran, obj, targetID): #added action passthrough for wormhole move...needed
1048
        # DON'T move fleet with speed == 0
1049
        if obj.speed <= 0:
1050
            # they cannot arive (never)
1051
            # reset retreat counter
1052
            obj.combatRetreatWait = 0
1053
            return 1
1054
        if targetID == Const.OID_NONE:
1055
            # reset retreat counter
1056
            obj.combatRetreatWait = 0
1057
            return 1
1058
        # reset/remember old values
1059
        obj.oldX = obj.x
1060
        obj.oldY = obj.y
1061
        obj.eta = 0.0
1062
        target = tran.db[targetID]
1063
        obj.target = targetID
1064
        # MOVE to target
1065
        dx = target.x - obj.x
1066
        dy = target.y - obj.y
1067
        #if dx == 0 and dy == 0:
1068
        #    return 1
1069
        if obj.orbiting:
1070
            system = tran.db[obj.orbiting]
1071
            if system.combatCounter > 0:
1072
                # well, there is a combat there -> wait a while and reduce ROF
1073
                obj.combatRetreatWait += 1
1074
                if obj.combatRetreatWait <= Rules.combatRetreatWait:
1075
                    return 0
1076
                # ok, we suffered enough, move away
1077
            # reset counter
1078
            obj.combatRetreatWait = 0
1079
            # speed boost?
1080
            obj.speedBoost = Utils.getSpeedBoost(tran, tran.db[obj.owner], (system, target))
1081
            #
1082
            try:
1083
                system.fleets.remove(obj.oid)
1084
            except ValueError:
1085
                log.warning('IFleet', 'Problem with removing fleet from system.')
1086
            obj.orbiting = Const.OID_NONE
1087
            # change close system to target one
1088
            if obj.closeSystem != Const.OID_NONE: # TODO remove condition in 0.6
1089
                system = tran.db[obj.closeSystem]
1090
                try:
1091
                    system.closeFleets.remove(obj.oid)
1092
                except ValueError:
1093
                    log.warning("IFleet", "Problem with changing the close system.")
1094
            if target.type == Const.T_PLANET:
1095
                system = tran.db[target.compOf]
1096
                system.closeFleets.append(obj.oid)
1097
                obj.closeSystem = system.oid
1098
            elif target.type in (Const.T_SYSTEM, Const.T_WORMHOLE):
1099
                target.closeFleets.append(obj.oid)
1100
                obj.closeSystem = target.oid
1101
            else:
1102
                raise GameException('Unsupported type of target %d for move command.' % target.type)
1103
        dist = math.hypot(dx, dy)
1104
        maxDelta = obj.speed / Rules.turnsPerDay * obj.speedBoost
1105
        if not maxDelta:
1106
            obj.combatRetreatWait = 0
1107
            return 0
1108
        arrived = 0
1109
        # 0.01 acceptable error
1110
        if dist <= maxDelta + 0.01:
1111
            # we are at destination
1112
            obj.x = target.x
1113
            obj.y = target.y
1114
            if target.type == Const.T_PLANET:
1115
                obj.orbiting = target.compOf
1116
                system = tran.db[obj.orbiting]
1117
                system.fleets.append(obj.oid)
1118
                arrived = 1
1119
            elif target.type == Const.T_SYSTEM or target.type == Const.T_WORMHOLE:
1120
                #@log.debug('IFleet', obj.oid, 'is aproaching orbit of', targetID)
1121
                obj.orbiting = target.oid
1122
                system = tran.db[obj.orbiting]
1123
                system.fleets.append(obj.oid)
1124
                #@log.debug('IFleet', system.oid, 'system fleets', system.fleets)
1125
                arrived = 1
1126
            else:
1127
                raise GameException('Unsupported type of target %d for move command.' % target.type)
1128
        else:
1129
            # move
1130
            obj.x += dx / dist * maxDelta
1131
            obj.y += dy / dist * maxDelta
1132
            # (already moved 1 x maxDelta) (0.01 is acceptable error)
1133
            obj.eta = math.ceil(dist / maxDelta - 1 - 0.01)
1134
        if arrived:
1135
            obj.target = Const.OID_NONE
1136
            # just make sure that this is reset
1137
            obj.combatRetreatWait = 0
1138
            # turn scanner on
1139
            obj.scannerOn = True
1140
            # check the speed boost
1141
            speedBoost = Utils.getSpeedBoost(tran, tran.db[obj.owner], (system,))
0 ignored issues
show
introduced by
The variable system does not seem to be defined in case obj.orbiting on line 1069 is False. Are you sure this can never be the case?
Loading history...
1142
            if speedBoost < obj.speedBoost:
1143
                # damage all ships in the fleet
1144
                # damage is based on percentual difference
1145
                percHull = 1.0 - Rules.starGateDamage * (obj.speedBoost / speedBoost - 1.0)
1146
                log.debug(obj.oid, "fleet speed boost too low - damaging ships", speedBoost, obj.speedBoost, percHull)
1147
                Utils.sendMessage(tran, obj, Const.MSG_DAMAGE_BY_SG, obj.orbiting, int((1.0 - percHull) * 100))
1148
                for ship in obj.ships:
1149
                    ship[Const.SHIP_IDX_HP] = max(1, int(ship[Const.SHIP_IDX_HP] * percHull))
1150
                # TODO: send message to player
1151
            obj.speedBoost = 1.0
1152
            # add ship to the scanner pwrs of the system
1153
            system.scannerPwrs[obj.owner] = max(obj.scannerPwr, system.scannerPwrs.get(obj.owner, 0))
1154
        return arrived
1155
1156
    moveToTarget.public = 0
1157
1158
    @public(Const.AL_ADMIN)
1159
    def processFINALPhase(self, tran, obj, data):
1160
        # stats
1161
        player = tran.db[obj.owner]
1162
        player.stats.fleetPwr += obj.combatPwr
1163
        player.stats.fleetSupportProd += obj.operProd
1164
        #
1165
1166
    ##
1167
    ## Combat related functions
1168
    ##
1169
1170
    def getPreCombatData(self, tran, obj):
1171
        # compute data
1172
        shots = {0: [], 1: [], 2: [], 3: []}
1173
        targets = [0, 0, 0, 0]
1174
        player = tran.db[obj.owner]
1175
        desCount = {}
1176
        firing = False
1177
        rofMod = 1.0
1178
        # limit number of shots per ship
1179
        obj.maxHits = {0: 0, 1: 0, 2: 0, 3: 0}
1180
        obj.hitCounters = {0: 0, 1: 0, 2: 0, 3: 0}
1181
        obj.lastHitClass = 3
1182
        obj.hitMods = {0: 1.0, 1: 1.0, 2: 1.0, 3: 1.0}
1183
1184
        if obj.combatRetreatWait > 0:
1185
            # ROF penalty
1186
            #@log.debug(obj.oid, "Fleet inactive", obj.combatRetreatWait)
1187
            rofMod *= 0.33
1188
        if obj.storEn == 0:
1189
            rofMod *= 0.33
1190
        for designID, hp, shield, exp in obj.ships:
1191
            tech = player.shipDesigns[designID]
1192
            targets[tech.combatClass] += 1
1193
            desCount[designID] = desCount.get(designID, 0) + 1
1194
            obj.maxHits[tech.combatClass] += 2
1195
            wpnCount = {}
1196
            for weaponID in tech.weaponIDs:
1197
                firing = True
1198
                weapon = Rules.techs[weaponID]
1199
                wpnCount[weaponID] = wpnCount.get(weaponID, 0) + 1
1200
                #
1201
                weaponEff = Rules.techImprEff[player.techs.get(weaponID, Rules.techBaseImprovement)]
1202
                # base attack
1203
                attack = (tech.combatAtt + int(weapon.weaponAtt * weaponEff)) * tech.combatAttMultiplier #added multiplier part
1204
                # correct using ship's level
1205
                level = Rules.shipExpToLevel.get(int(exp / tech.baseExp), Rules.shipDefLevel)
1206
                attack = int(attack * Rules.shipLevelEff[level])
1207
                # because ALL counters starts at 1, subtract 3
1208
                count = obj.combatCounter + desCount[designID] + wpnCount[weaponID] - 3
1209
                # add to attacks
1210
                #@log.debug('IFleet', obj.oid, designID, "Count", count, 'Shots', weapon.name, ShipUtils.getRounds(weapon.weaponROF, count))
1211
                for round in xrange(0, ShipUtils.getRounds(weapon.weaponROF * rofMod, count)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
1212
                    shots[weapon.weaponClass].append((attack, weaponID))
1213
        log.debug(obj.oid, "Combat limit settings", obj.maxHits)
1214
        return shots, targets, firing
1215
1216
    getPreCombatData.public = 0
1217
1218
    def applyMine(self, tran, obj, attack, damage, ignoreshield):
1219
        player = tran.db[obj.owner]
1220
        targetindex = random.randrange(0,len(obj.ships))
1221
        designID, hp, shield, exp = obj.ships[targetindex]
1222
        targetShip = player.shipDesigns[designID]
1223
        level = Rules.shipExpToLevel.get(int(exp / targetShip.baseExp), Rules.shipDefLevel)
1224
        defense = int(targetShip.missileDef * Rules.shipLevelEff[level])
1225
        #determine damage:
1226
        defenseBase = 4 #normal enemy defense to use as part of the ratio
1227
        damageRatio = min(max(1.0*(attack + defenseBase) / (attack + defense),0.25),1.25) #the better the defense, the less damage you take from the mine: 25% to 125% damage of normal mine
1228
        damage = int(damage * damageRatio)
1229
        if not damage:
1230
            return 0,0 #mine did no damage due to low ATT value on mine
1231
        #do damage:
1232
        destroyed = 0
1233
        blocked = 0
1234
        if not ignoreshield and shield > 0:
1235
            blocked = min(shield, damage)
1236
            obj.ships[targetindex][2] -= blocked
1237
            damage -= blocked
1238
        elif ignoreshield and targetShip.hardShield > 0 and shield > 0:
1239
            blocked = min(shield, int(damage*(ship.hardShield))) #hard shields also reduce penetrating weapons
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ship does not seem to be defined.
Loading history...
1240
            obj.ships[targetindex][2] -= blocked
1241
            damage -= blocked
1242
        if shield: #mines never pierce shields at this time; possible future expansion of the tech
1243
            blocked = min(shield, damage)
1244
            damage -= blocked
1245
            obj.ships[targetindex][2] -= blocked
1246
        if damage > 0:
1247
            if hp < damage:
1248
                damage = hp
1249
                destroyed = 1
1250
                self.cmd(obj).removeShips(tran, obj, [obj.ships[targetindex]])
1251
            else:
1252
                obj.ships[targetindex][1] -= damage
1253
        return damage + blocked, destroyed
1254
1255
    applyMine.public = 0
1256
1257
    def applyShot(self, tran, obj, defense, attack, weaponID, targetClass, target):
1258
        #@log.debug(obj.oid, 'IFleet', 'Apply shot', attack, weaponID, targetClass, target)
1259
        player = tran.db[obj.owner]
1260
        # find correct ship to hit
1261
        target = -1
1262
        targetCiv = 0
1263
        while target == -1:
1264
            index = 0
1265
            found = 0
1266
            for designID, hp, shield, exp in obj.ships:
1267
                design = player.shipDesigns[designID]
1268
                if design.combatClass == targetClass and (design.isMilitary or targetCiv):
1269
                    found = 1
1270
                    if Utils.rand(1, 101) < Rules.shipTargetPerc[targetClass]:
1271
                        target = index
1272
                        break
1273
                index += 1
1274
            if not targetCiv:
1275
                targetCiv = 1
1276
                continue
1277
            if not found and targetCiv:
1278
                # no such target class - try to find another one
1279
                log.warning("No such target class in the fleet", obj.oid, targetClass)
1280
                targetClass = targetClass + 1
1281
                targetCiv = 0
1282
                if targetClass > 3:
1283
                    return 0, 0, 0
1284
        designID, hp, shield, exp = obj.ships[target]
1285
        ship = player.shipDesigns[designID]
1286
        # compute if ship has been hit
1287
        weapon = Rules.techs[weaponID]
1288
        level = Rules.shipExpToLevel.get(int(exp / ship.baseExp), Rules.shipDefLevel)
1289
        # add system defense bonus to ship inate defense
1290
        if weapon.weaponIsMissile:
1291
            defense += int(ship.missileDef * Rules.shipLevelEff[level])
1292
        else:
1293
            defense += int(ship.combatDef * Rules.shipLevelEff[level])
1294
        destroyed = 0
1295
        destroyedClass = ship.combatClass
1296
        dmg = 0
1297
        blocked = 0
1298
        # limit number of shots
1299
        cClass = weapon.weaponClass
1300
        if cClass < obj.lastHitClass:
1301
            #@log.debug(obj.oid, "Different class", obj.lastHitClass, cClass, obj.maxHits)
1302
            for i in range(obj.lastHitClass - 1, cClass - 1, -1):
1303
                if obj.hitMods[cClass] >= 0.99: # == 1.0
1304
                    #@log.debug(obj.oid, "Adding to", i, int(Rules.combatHitXferMod * (obj.maxHits[i + 1] - obj.hitCounters[i + 1])), obj.hitCounters[i + 1])
1305
                    obj.maxHits[i] += int(Rules.combatHitXferMod * (obj.maxHits[i + 1] - obj.hitCounters[i + 1]))
1306
                else:
1307
                    #@log.debug(obj.oid, "Not transfering hits")
1308
                    pass
1309
                obj.maxHits[i + 1] = 0
1310
            #@log.debug(obj.oid, "max hits", obj.maxHits)
1311
            obj.lastHitClass = cClass
1312
        elif cClass > obj.lastHitClass:
1313
            log.debug(obj.oid, "INCORRECT ORDER OF SHOTS", obj.lastHitClass, cClass)
1314
        if weapon.weaponROF > 1:
1315
            #@log.debug(obj.oid, "Increasing counter", cClass, 1.0 / weapon.weaponROF)
1316
            obj.hitCounters[cClass] += 1.0 / weapon.weaponROF
1317
        else:
1318
            #@log.debug(obj.oid, "Increasing counter", cClass, 1)
1319
            obj.hitCounters[cClass] += 1
1320
        if obj.hitCounters[cClass] > obj.maxHits[cClass]:
1321
            obj.hitCounters[cClass] = 0
1322
            obj.hitMods[cClass] *= Rules.combatShipHitMod
1323
            #@log.debug(obj.oid, "Increasing hit penalty", obj.hitMods[cClass], obj.maxHits[cClass], "class", cClass)
1324
        #
1325
        attackChance = obj.hitMods[cClass] * attack / (attack + defense)
1326
        #@log.debug(obj.oid, "Chance to attack", attackChance, obj.hitMods[cClass],
1327
        #@     obj.hitCounters[cClass], obj.maxHits[cClass], "without penalty:", float(attack) / (attack + defense))
1328
        if random.random() <= attackChance:
1329
            player = tran.db[obj.owner]
1330
            weaponEff = Rules.techImprEff[player.techs.get(weaponID, Rules.techBaseImprovement)]
1331
            # HIT! -> apply damage
1332
            dmg = ShipUtils.computeDamage(weapon.weaponClass, ship.combatClass, weapon.weaponDmgMin, weapon.weaponDmgMax, weaponEff)
1333
            #@log.debug(obj.oid, 'HIT! att=%d vs def=%d, dmg=%d '% (attack, defense, dmg))
1334
            # shield
1335
            if not weapon.weaponIgnoreShield and shield > 0:
1336
                blocked = min(shield, dmg)
1337
                obj.ships[target][2] -= blocked
1338
                dmg -= blocked
1339
            elif weapon.weaponIgnoreShield and ship.hardShield > 0 and shield > 0:
1340
                blocked = min(shield, int(dmg*(ship.hardShield))) #hard shields also reduce penetrating weapons
1341
                obj.ships[target][2] -= blocked
1342
                dmg -= blocked
1343
            #damage absorbsion by armor
1344
            if ship.damageAbsorb > 0 and dmg > 0:
1345
                dmg = max(0,dmg-ship.damageAbsorb)
1346
            # armour
1347
            if dmg >= hp:
1348
                destroyed = 1
1349
                self.cmd(obj).removeShips(tran, obj, [obj.ships[target]])
1350
                dmg = hp
1351
            else:
1352
                obj.ships[target][1] -= dmg
1353
        #@log.debug(obj.oid, "Damaged", dmg, blocked, destroyed)
1354
        return dmg + blocked, destroyed, destroyedClass
1355
1356
    applyShot.public = 0
1357
1358
    def distributeExp(self, tran, obj):
1359
        # TODO improve
1360
        player = tran.db[obj.owner]
1361
        while obj.combatExp > 0:
1362
            haveMilitary = 0
1363
            for ship in obj.ships:
1364
                # ignore civilian ships
1365
                if not player.shipDesigns[ship[0]].isMilitary:
1366
                    continue
1367
                # add exp point
1368
                haveMilitary = 1
1369
                ship[3] += 1
1370
                obj.combatExp -= 1
1371
                if obj.combatExp == 0:
1372
                    break
1373
            if not haveMilitary:
1374
                break
1375
        del obj.maxHits
1376
        del obj.hitCounters
1377
        del obj.lastHitClass
1378
        del obj.hitMods
1379
1380
    distributeExp.public = 0
1381
1382
    def surrenderTo(self, tran, obj, newOwnerID):
1383
        # we've lost the battle - issue MOVE command to the nearest player's star
1384
        return 0
1385
1386
    surrenderTo.public = 0
1387