ige.ospace.IFleet.IFleet.moveToWormhole()   C
last analyzed

Complexity

Conditions 10

Size

Total Lines 31
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 28
nop 4
dl 0
loc 31
rs 5.9999
c 0
b 0
f 0

How to fix   Complexity   

Complexity

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