IPlanet._processEnvironmentChange()   F
last analyzed

Complexity

Conditions 17

Size

Total Lines 50
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 42
nop 4
dl 0
loc 50
rs 1.8
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like ige.ospace.IPlanet.IPlanet._processEnvironmentChange() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Outer Space.
5
#
6
#  Outer Space is free software; you can redistribute it and/or modify
7
#  it under the terms of the GNU General Public License as published by
8
#  the Free Software Foundation; either version 2 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Outer Space is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
#
16
#  You should have received a copy of the GNU General Public License
17
#  along with Outer Space; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import copy
22
import math
23
import random
24
25
from xml.dom.minidom import Node
26
27
import ige
28
import Const
29
import Rules
30
import Utils
31
import ShipUtils
32
33
from ige import log
34
from ige.IObject import IObject, public
35
from ige.IDataHolder import IDataHolder
36
37
class IPlanet(IObject):
38
39
    typeID = Const.T_PLANET
40
41
    def init(self, obj):
42
        IObject.init(self, obj)
43
        #
44
        obj.x = 0.0
45
        obj.y = 0.0
46
        obj.plDiameter = 0
47
        obj.plType = u'-'
48
        obj.plMin = 0
49
        obj.plBio = 0
50
        obj.plEn = 0
51
        obj.plEnv = 0
52
        obj.plSlots = 0
53
        obj.plMaxSlots = 0
54
        obj.plStratRes = 0
55
        obj.plDisease = 0
56
        obj.plStarting = 0
57
        obj.orbit = 0
58
        obj.storPop = 0
59
        obj.slots = []
60
        obj.lastPirCapture = 0
61
        # storage
62
        obj.storBio = 0
63
        obj.storEn = 0
64
        obj.minBio = Rules.colonyMinBio
65
        obj.minEn = Rules.colonyMinEn
66
        obj.maxBio = 0
67
        obj.maxEn = 0
68
        # changes/prod
69
        obj.prodQueue = []
70
        obj.globalQueue = 0
71
        obj.changeBio = 0
72
        obj.changeEn = 0
73
        obj.changePop = 0
74
        obj.changeEnv = 0
75
        obj.prodProd = 0
76
        obj.effProdProd = 0
77
        obj.prodSci = 0
78
        obj.effProdSci = 0
79
        obj.unemployedPop = 0
80
        # eating / housing
81
        obj.popEatBio = 10
82
        obj.popEatEn = 0
83
        obj.maxPop = 0
84
        # extra goodies
85
        obj.solarmod = 0
86
        obj.scannerPwr = 0
87
        obj.signature = 75
88
        obj.autoMinStor = 1
89
        obj.morale = Rules.maxMorale
90
        obj.changeMorale = 0.0
91
        obj.moraleTrgt = 0.0
92
        # moraleModifiers [ base morale by distance from homeworld, from buildings, from population, from unemployment, summary 1+2+3+4 ]
93
        obj.moraleModifiers = [0.0, 0.0, 0.0, 0.0, 0.0]
94
        obj.revoltLen = 0
95
        obj.combatExp = 0
96
        obj.isMilitary = 0
97
        obj.refuelMax = 0
98
        obj.refuelInc = 0
99
        obj.repairShip = 0.0
100
        obj.upgradeShip = 0.0
101
        obj.trainShipInc = 0
102
        obj.trainShipMax = 0
103
        obj.fleetSpeedBoost = 1.0
104
        obj.ownerSince = 0
105
        obj.shield = 0          #current planetary shield level
106
        obj.maxShield = 0       #structural max sheild (best structure method)
107
        obj.prevShield = 0      #previous turn's shield level (for client growth calculation)
108
109
    @public(Const.AL_FULL)
110
    def startConstruction(self, tran, obj, techID, quantity, targetID, isShip, reportFinished,
111
        demolishStruct):
112
        if len(obj.prodQueue) > Rules.maxProdQueueLen:
113
            raise ige.GameException('Queue is full.')
114
        if quantity < 1:
115
            raise ige.GameException("Quantity must be greater than 0")
116
        player = tran.db[obj.owner]
117
        if not player.techs.has_key(techID) and isShip == 0:
118
            raise ige.GameException('You do not own this kind of technology.')
119
        if not player.shipDesigns.has_key(techID) and isShip == 1:
120
            raise ige.GameException('You do not own this ship design.')
121
        if targetID not in tran.db[obj.compOf].planets:
122
            raise ige.GameException('You can build only in the same system.')
123
        if isShip:
124
            tech = player.shipDesigns[techID]
125
            if tech.upgradeTo:
126
                raise ige.GameException("You cannot build obsolete ship design.")
127
        else:
128
            tech = Rules.techs[techID]
129
            if not (tech.isStructure or tech.isProject):
130
                raise ige.GameException('You cannot construct this technology.')
131
            if not tech.validateConstrHandler(tran, obj, tran.db[targetID], tech):
132
                raise ige.GameException('Conditions for construction are not satisfied.')
133
        neededSR = {}
134
        for sr in tech.buildSRes:
135
            nSR = neededSR.get(sr, 0) + quantity * tech.buildSRes[sr]
136
            if player.stratRes.get(sr, 0) < nSR:
137
                raise ige.GameException("You do not own enough of required strategic resource(s)")
138
            neededSR[sr] = nSR
139
        # consume strategic resources
140
        for sr in neededSR:
141
            player.stratRes[sr] -= neededSR[sr]
142
        # start construction
143
        item = IDataHolder()
144
        item.techID = techID
145
        item.currProd = 0
146
        item.currTurn = 0
147
        item.quantity = int(quantity)
148
        item.targetID = targetID
149
        item.changePerc = 0
150
        item.isShip = bool(isShip)
151
        item.reportFin = bool(reportFinished)
152
        item.demolishStruct = demolishStruct
153
        item.type = Const.T_TASK
154
        obj.prodQueue.append(item)
155
        return obj.prodQueue, player.stratRes
156
157
    @public(Const.AL_FULL)
158
    def changeConstruction(self, tran, obj, index, quantity):
159
        if index < 0 or index >= len(obj.prodQueue):
160
            raise ige.GameException("No such item in the construction queue.")
161
162
        if quantity < 1:
163
            raise ige.GameException("Quantity must be greater than 0")
164
165
        player = tran.db[obj.owner]
166
        item = obj.prodQueue[index]
167
        if item.isShip:
168
            tech = player.shipDesigns[item.techID]
169
        else:
170
            tech = Rules.techs[item.techID]
171
172
        quantityChange = quantity - obj.prodQueue[index].quantity
173
174
        neededSR = {}
175
        for sr in tech.buildSRes:
176
            nSR = neededSR.get(sr, 0) + quantityChange * tech.buildSRes[sr]
177
            if player.stratRes.get(sr, 0) < nSR:
178
                raise ige.GameException("You do not own enough of required strategic resource(s)")
179
            neededSR[sr] = nSR
180
        # consume strategic resources
181
        for sr in neededSR:
182
            player.stratRes[sr] -= neededSR[sr]
183
184
        obj.prodQueue[index].quantity = quantity
185
        return obj.prodQueue, player.stratRes
186
187
    @public(Const.AL_FULL)
188
    def abortConstruction(self, tran, obj, index):
189
        if index >= len(obj.prodQueue):
190
            raise ige.GameException('No such item in the construction queue.')
191
        # Free strategic resources
192
        player = tran.db[obj.owner]
193
        item = obj.prodQueue[index]
194
        if item.isShip:
195
            tech = player.shipDesigns[item.techID]
196
        else:
197
            tech = Rules.techs[item.techID]
198
        for sr in tech.buildSRes:
199
            player.stratRes[sr] = player.stratRes.get(sr, 0) + item.quantity * tech.buildSRes[sr]
200
        # delete task
201
        del obj.prodQueue[index]
202
        return obj.prodQueue, player.stratRes
203
204
    @public(Const.AL_FULL)
205
    def moveConstrItem(self, tran, obj, index, rel):
206
        if index >= len(obj.prodQueue):
207
            raise ige.GameException('No such item in the construction queue.')
208
        if index + rel < 0 or index + rel >= len(obj.prodQueue):
209
            raise ige.GameException('Cannot move.')
210
        item = obj.prodQueue[index]
211
        del obj.prodQueue[index]
212
        obj.prodQueue.insert(index + rel, item)
213
        return obj.prodQueue
214
215
    @public(Const.AL_ADMIN)
216
    def changeOwner(self, tran, obj, ownerID, force = 0):
217
        oldOwnerID = obj.owner
218
        if obj.owner == ownerID:
219
            # the owner is the same
220
            return
221
        elif obj.owner != Const.OID_NONE and force == 0:
222
            # this planet is already owned!
223
            # TODO resolve conflict (based on player relations)
224
            raise ige.GameException('Planet is already owned by another commander.')
225
        elif obj.owner != Const.OID_NONE and force == 1:
226
            # remove planet from old owner
227
            try:
228
                oldOwner = tran.db[obj.owner]
229
                oldOwner.planets.remove(obj.oid)
230
                if tran.db.has_key(obj.owner):
231
                    Utils.sendMessage(tran, obj, Const.MSG_LOST_PLANET, obj.oid, None)
232
            except Exception:
233
                log.warning("Cannot remove planet from owner", obj.oid, obj.owner)
234
                oldOwnerID = Const.OID_NONE
235
        # reset timer
236
        obj.ownerSince = tran.db[Const.OID_UNIVERSE].turn
237
        # add planet to new owner's empire
238
        if ownerID != Const.OID_NONE:
239
            newOwner = tran.db[ownerID]
240
            newOwner.planets.append(obj.oid)
241
        # reset some attributes
242
        obj.owner = ownerID
243
        obj.revoltLen = 0 # no revolt
244
        obj.prodQueue = [] # clear production queue
245
        obj.globalQueue = 0 # default global queue
246
        obj.autoMinStor = 1 # storage is set to auto
247
        if ownerID != Const.OID_NONE:
248
            # notify player
249
            Utils.sendMessage(tran, obj, Const.MSG_GAINED_PLANET, obj.oid, None)
250
251
    @public(Const.AL_FULL)
252
    def setStructOn(self, tran, obj, slotIdx, on):
253
        if slotIdx >= len(obj.slots) or slotIdx < 0:
254
            raise ige.GameException('No such structure.')
255
        if on:
256
            obj.slots[slotIdx][Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_ON
257
        else:
258
            obj.slots[slotIdx][Const.STRUCT_IDX_STATUS] &= ~Const.STRUCT_STATUS_ON
259
        return obj.slots[slotIdx]
260
261
    @public(Const.AL_FULL)
262
    def demolishStruct(self, tran, obj, slotIdx):
263
        # TODO implement special button for demolishing structures when
264
        # planet surrenders
265
        #isCombat = tran.db[obj.compOf].combatCounter > 0
266
        #if isCombat and len(obj.slots) < obj.plSlots:
267
        #    raise ige.GameException("You cannot destroy this structure under fire - at least one slot is free.")
268
        if slotIdx >= len(obj.slots) or slotIdx < 0:
269
            raise ige.GameException('No such structure.')
270
        del obj.slots[slotIdx]
271
        return obj.slots
272
273
    @public(Const.AL_FULL)
274
    def moveStruct(self, tran, obj, slotIdx, rel):
275
        if slotIdx >= len(obj.slots) or slotIdx < 0:
276
            raise ige.GameException('No such structure.')
277
        if slotIdx + rel < 0 or slotIdx + rel >= len(obj.slots):
278
            raise ige.GameException('Cannot move.')
279
        struct = obj.slots[slotIdx]
280
        del obj.slots[slotIdx]
281
        obj.slots.insert(slotIdx + rel, struct)
282
        return obj.slots
283
284
    @public(Const.AL_ADMIN)
285
    def processINITPhase(self, tran, obj, data):
286
        # get rid of the NEW states
287
        for struct in obj.slots:
288
            struct[Const.STRUCT_IDX_STATUS] &= Const.STRUCT_STATUS_RESETFLGS
289
290
    def _getStructProdMod(self, obj, techProdMod):
291
        b, m, e, d = techProdMod
292
        return (b * obj.plBio + m * obj.plMin + e * obj.plEn + d * 100) / 100
293
294
    def _getOpStatus(self, obj, techProdMod, techOper, techProd, stor):
295
        prodMod = self._getStructProdMod(obj, techProdMod)
296
        slope = techProd * prodMod - techOper
297
        if slope >= 0:
298
            # structure is self-sufficient in this aspect
299
            return 1.0
300
        else:
301
            return min(stor / - slope, 1.0)
302
303
    def _getStructStatus(self, obj, struct, tech, maxHP):
304
        # find most limitating condition
305
        if not struct[Const.STRUCT_IDX_STATUS] & Const.STRUCT_STATUS_ON:
306
            return 0.0, 0.0
307
        try:
308
            opStatusHP = min(1.0, float(struct[Const.STRUCT_IDX_HP]) / maxHP)
309
        except:
310
            opStatusHP = 0.0
311
            log.warning('Invalid max HP of structure', Const.STRUCT_IDX_TECHID)
312
        opStatusBio = self._getOpStatus(obj, tech.prodBioMod, tech.operBio, tech.prodBio, obj.storBio)
313
        opStatusEn = self._getOpStatus(obj, tech.prodEnMod, tech.operEn, tech.prodEn, obj.storEn)
314
        opStatusPop = min(1.0, float(obj.unemployedPop) / tech.operWorkers)
315
        opStatus = min(opStatusHP, opStatusBio, opStatusEn, opStatusPop)
316
        if opStatus < 1.0:
317
            if opStatusBio == opStatus:
318
                struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_NOBIO
319
            if opStatusEn == opStatus:
320
                struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_NOEN
321
            if opStatusPop == opStatus:
322
                struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_NOPOP
323
        struct[Const.STRUCT_IDX_OPSTATUS] = int(100 * opStatus)
324
        return opStatusHP, min(opStatusBio, opStatusEn, opStatusPop)
325
326
    def _updateStructHP(self, obj, struct, tech, opStatuses, maxHP):
327
        # auto repair/damage
328
        # also damage structures on not owned planets
329
        opStatusHP, opStatusOther = opStatuses
330
        properHP = opStatusOther * maxHP
331
        if struct[Const.STRUCT_IDX_HP] < properHP:
332
            repairDiff = min(properHP - struct[Const.STRUCT_IDX_HP], Rules.repairRatioFunc(tech.buildProd) * maxHP)
333
            struct[Const.STRUCT_IDX_HP] += repairDiff
334
            struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_REPAIRING
335
        elif struct[Const.STRUCT_IDX_HP] > properHP:
336
            decayDiff = min(struct[Const.STRUCT_IDX_HP] - properHP, Rules.decayRatioFunc(tech.buildProd) * maxHP)
337
            struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_DETER
338
            # damage it a bit
339
            struct[Const.STRUCT_IDX_HP] -= decayDiff
340
            if obj.storPop > 0:
341
                # do not fall below 1 HP for populated planets so it won't destroy buildings
342
                struct[Const.STRUCT_IDX_HP] = max(struct[Const.STRUCT_IDX_HP], 1)
343
        if struct[Const.STRUCT_IDX_HP] <= 0:
344
            obj.slots.remove(struct)
345
346
    def _processStructs(self, tran, obj):
347
        for struct in obj.slots[:]:
348
            # skip structure if it was built this turn
349
            if struct[Const.STRUCT_IDX_STATUS] & Const.STRUCT_STATUS_NEW:
350
                continue
351
            tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
352
            # compute struct effectivity
353
            techEff = Utils.getTechEff(tran, struct[Const.STRUCT_IDX_TECHID], obj.owner)
354
            # morale does not affect hit points of structures
355
            maxHP = int(tech.maxHP * techEff)
356
            # auto regulation of min resources
357
            if obj.autoMinStor:
358
                obj.minBio += tech.operBio * Rules.autoMinStorTurns
359
                obj.minEn += tech.operEn * Rules.autoMinStorTurns
360
            # produce/consume resources
361
            opStatuses = self._getStructStatus(obj, struct, tech, maxHP)
362
            self._updateStructHP(obj, struct, tech, opStatuses, maxHP)
363
            opStatus = min(opStatuses)
364
            # solarmod effects ENV change and terraforming only if benificial
365
            if tech.solarMod * opStatus > 0:
366
                obj.solarmod = max(obj.solarmod, tech.solarMod * techEff * opStatus)
367
            elif tech.solarMod * opStatus < 0:
368
                obj.solarmod = min(obj.solarmod, tech.solarMod * techEff * opStatus)
369
            # bio
370
            prodMod = self._getStructProdMod(obj, tech.prodBioMod)
371
            obj.storBio += int(tech.prodBio * prodMod * techEff * opStatus) - int(tech.operBio * opStatus)
372
            # en
373
            prodMod = self._getStructProdMod(obj, tech.prodEnMod)
374
            obj.storEn += int(tech.prodEn * prodMod * techEff * opStatus) - int(tech.operEn * opStatus)
375
376
            obj.unemployedPop -= min(obj.unemployedPop, int(tech.operWorkers * opStatus))
377
            obj.storPop += int(tech.prodPop * techEff * opStatus)
378
            obj.scannerPwr = max(int(tech.scannerPwr * techEff * opStatus), obj.scannerPwr)
379
            obj.scannerPwr = min(obj.scannerPwr, Rules.scannerMaxPwr)
380
            # rebellion and combat has common penalty
381
            prodMod = self._getStructProdMod(obj, tech.prodProdMod)
382
            obj.prodProd += int(tech.prodProd * prodMod * techEff * opStatus)
383
            # science
384
            prodMod = self._getStructProdMod(obj, tech.prodSciMod)
385
            obj.prodSci += int(tech.prodSci * prodMod * techEff * opStatus)
386
            # refuelling & repairing
387
            obj.refuelMax = max(obj.refuelMax, int(tech.refuelMax * techEff * opStatus))
388
            # refuelling
389
            obj.refuelInc = max(obj.refuelInc, int(tech.refuelInc * techEff * opStatus))
390
            # repair
391
            obj.repairShip += tech.repairShip * techEff * opStatus
392
            obj.upgradeShip += tech.upgradeShip * techEff * opStatus
393
            # train
394
            obj.trainShipMax = max(obj.trainShipMax, tech.trainShipMax)
395
            obj.trainShipInc = max(obj.trainShipInc, tech.trainShipInc * techEff * opStatus)
396
            # shielding
397
            obj.maxShield = max(tech.planetShield * techEff * opStatus, obj.maxShield)
398
            # stargates
399
            obj.fleetSpeedBoost = max(obj.fleetSpeedBoost, tech.fleetSpeedBoost * techEff * opStatus)
400
            # storage
401
            obj.maxBio += int(tech.storBio * techEff)
402
            obj.maxEn += int(tech.storEn * techEff)
403
            # each structure accomodate it's workers
404
            obj.maxPop += tech.operWorkers
405
            obj.maxPop += int(tech.storPop * techEff)
406
            obj.plEnv += int(tech.prodEnv * techEff * opStatus)
407
            # morale modifier of the building
408
            obj.moraleModifiers[1] += tech.moraleTrgt * techEff * opStatus
409
410
    def _processPopulation(self, obj, owner):
411
        if not obj.storPop:
412
            return
413
        # population reserve
414
        obj.maxPop += obj.plSlots * getattr(owner, "techLevel", 1) * Rules.tlPopReserve
415
        # max pop
416
        maxPop = obj.maxPop
417
        if obj.popEatBio: maxPop = min(maxPop,  1000.0 * obj.storBio / obj.popEatBio)
418
        if obj.popEatEn: maxPop = min(maxPop, 1000.0 * obj.storEn / obj.popEatEn)
419
        maxPop = int(maxPop)
420
        # eat
421
        pop = obj.storPop / 1000.0
422
        wantBio = int(math.ceil(pop * obj.popEatBio))
423
        wantEn = int(math.ceil(pop * obj.popEatEn))
424
        # auto regulation of min resources
425
        if obj.autoMinStor:
426
            obj.minBio += wantBio * Rules.autoMinStorTurns
427
            obj.minEn += wantEn * Rules.autoMinStorTurns
428
        # consume resources
429
        obj.storBio -= min(obj.storBio, wantBio)
430
        obj.storEn -= min(obj.storEn, wantEn)
431
        # modify pop
432
        if obj.storPop > maxPop:
433
            # die
434
            obj.storPop -= max(int((obj.storPop - maxPop) * Rules.popDieRate), Rules.popMinDieRate)
435
            #if obj.storPop < maxPop: obj.storPop = maxPop
436
            # do not generate this message when construction has been destroyed
437
            # and do not lower morale too
438
            if obj.storPop < obj.maxPop:
439
                obj.morale = max(obj.morale - Rules.moraleLostNoFood,0)
440
        elif obj.storPop < maxPop:
441
            # born
442
            obj.storPop += max(min(int(obj.storPop * Rules.popGrowthRate), maxPop - obj.storPop), Rules.popMinGrowthRate)
443
444
    def _buildShip(self, tran, obj, item, owner):
445
        system = tran.db[obj.compOf]
446
        # find commander's fleet
447
        fleet = None
448
        # check if current system has any redirection
449
        hasRedirection = obj.compOf in owner.shipRedirections
450
        for fleetID in system.fleets:
451
            tmpFleet = tran.db[fleetID]
452
            if tmpFleet.owner == obj.owner and Utils.isIdleFleet(tmpFleet):
453
                fleet = tmpFleet
454
                break
455
        if not fleet or hasRedirection:
456
            fleet = self.new(Const.T_FLEET)
457
            tran.db.create(fleet)
458
            self.cmd(fleet).create(tran, fleet, system, obj.owner)
459
            self.cmd(fleet).addAction(tran, fleet, 0, Const.FLACTION_REDIRECT, Const.OID_NONE, None)
460
        # add ships to the fleet
461
        self.cmd(fleet).addNewShip(tran, fleet, item.techID)
462
        if item.reportFin and item.quantity == 1:
463
            Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_SHIP, obj.oid, item.techID)
464
465
    def _buildStructure(self, tran, obj, item, tech, target):
466
        # if there is struct to demolish, find it, determine its CP value, and remove it
467
        validStructs = [struct for struct in target.slots if struct[Const.STRUCT_IDX_TECHID] == item.demolishStruct]
468
        if len(validStructs):
469
            assert item.demolishStruct != Const.OID_NONE
470
            oldStruct = validStructs[0]
471
            target.slots.remove(oldStruct)
472
            # replacement of structure means new struct will start in slightly more complete state
473
            oldStructTech = Rules.techs[item.demolishStruct]
474
            try:
475
                oldStructImpr = tran.db[obj.owner].techs[item.demolishStruct]
476
            except KeyError:
477
                oldStructImpr = 1
478
            maxHP = oldStructTech.maxHP * Rules.techImprEff[oldStructImpr]
479
            structValue = oldStructTech.buildProd * float(oldStruct[Const.STRUCT_IDX_HP]) / maxHP
480
            newStruct = Rules.techs[item.techID]
481
            buildHPRatio = min(Rules.structTransferMaxRatio,
482
                               Rules.structTransferWaste * structValue / newStruct.buildProd)
483
        else:
484
            buildHPRatio = Rules.structDefaultHpRatio
485
        if len(target.slots) < target.plSlots:
486
            target.slots.append(Utils.newStructure(tran, item.techID, obj.owner, hpRatio = buildHPRatio))
487
            try:
488
                tech.finishConstrHandler(tran, obj, target, tech)
489
            except Exception:
490
                log.warning("Cannot execute finish constr handler")
491
            if item.reportFin and item.quantity == 1:
492
                Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_STRUCTURE, target.oid, item.techID)
493
        else:
494
            # no free slot!
495
            Utils.sendMessage(tran, obj, Const.MSG_CANNOTBUILD_NOSLOT, target.oid, None)
496
497
    def _processProduction(self, tran, obj, owner):
498
        # produce items in construction queue
499
        if owner:
500
            moraleBonus = Rules.moraleProdBonus[int(obj.morale / Rules.moraleProdStep)]
501
            prod  = obj.effProdProd = max(0, int(obj.prodProd * (owner.prodEff + moraleBonus)))
502
            if (obj.morale > 15 and prod == 0 and obj.prodProd > 0 and owner.prodEff > 0): #added for super-low moral bonus issues
503
                prod  = obj.effProdProd = 1
504
        else:
505
            prod = obj.prodProd
506
        explicitIdleProd = 0.0
507
        # empty queue should be filled by global queue
508
        if len(obj.prodQueue) == 0 and prod:
509
            task = self.cmd(obj).popGlobalQueue(tran, obj)
510
            if task:
511
                obj.prodQueue.append(task)
512
        index = 0
513
        while prod > 0 and index < len(obj.prodQueue):
514
            item = obj.prodQueue[index]
515
            # check if owner has this tech
516
            if not item.isShip and item.techID not in owner.techs:
517
                # bad tech
518
                del obj.prodQueue[index]
519
                # TODO send message
520
            # set target
521
            target = tran.db[item.targetID]
522
            # set tech and build conditions
523
            if item.isShip:
524
                tech = tran.db[obj.owner].shipDesigns[item.techID]
525
                mod = Rules.buildOnSamePlanetMod
526
            else:
527
                tech = Rules.techs[item.techID]
528
                # check validity of the project
529
                if not tech.validateConstrHandler(tran, obj, target, tech):
530
                    index += 1
531
                    # message to player
532
                    Utils.sendMessage(tran, obj, Const.MSG_INVALID_TASK, obj.oid, item.techID)
533
                    continue
534
                # building on other planet is more expensive
535
                if item.targetID == obj.oid:
536
                    mod = Rules.buildOnSamePlanetMod
537
                else:
538
                    mod = Rules.buildOnAnotherPlanetMod
539
            # compute needs (do not consume resources under minimal storage)
540
            wantProd = min(int(tech.buildProd * mod / tech.buildTurns - item.currProd), prod)
541
            # production
542
            item.changePerc = wantProd * 10000 / (tech.buildProd * mod)
543
            # consume / produce
544
            if item.techID == Rules.Tech.IDLETASK and item.isShip == 0:
545
                explicitIdleProd += wantProd
546
            prod -= wantProd
547
            item.currProd += wantProd
548
            # check, if production is complete
549
            if item.currProd >= tech.buildProd * mod:
550
                # item is complete
551
                if item.isShip:
552
                    self._buildShip(tran, obj, item, owner)
553
                elif tech.isStructure:
554
                    self._buildStructure(tran, obj, item, tech, target)
555
                elif tech.isProject:
556
                    tech.finishConstrHandler(tran, obj, target, tech)
557
                    if item.reportFin and item.quantity == 1:
558
                        Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_PROJECT, target.oid, item.techID)
559
                else:
560
                    raise ige.GameException('Unsupported type of technology %d ' % item.techID)
561
                # remove item from prod queue
562
                item.quantity -= 1
563
                if item.quantity == 0:
564
                    # remove item from the queue
565
                    del obj.prodQueue[index]
566
                    # was it last item in the queue? pop the global one!
567
                    if index == len(obj.prodQueue):
568
                        task = self.cmd(obj).popGlobalQueue(tran, obj)
569
                        if task:
570
                            obj.prodQueue.append(task)
571
572
                else:
573
                    # try to produce another item
574
                    item.currProd = 0
575
            else:
576
                # item is not complete stop production
577
                index += 1
578
                break
579
        # decay items not currently produced
580
        while index < len(obj.prodQueue):
581
            item = obj.prodQueue[index]
582
            item.currProd -= int(item.currProd * Rules.decayProdQueue)
583
            index += 1
584
        # use excess raw CP to increase production elsewhere
585
        prod += explicitIdleProd
586
        if prod > 0.0:
587
            owner.prodIncreasePool += prod
588
589
    def _processEnvironmentChange(self, tran, obj, owner):
590
        downgradeTo = Rules.planetSpec[obj.plType].downgradeTo
591
        solarminus = min(0, obj.solarmod)
592
        solarplus = max(0, obj.solarmod)
593
        if downgradeTo is not None:
594
            if (Rules.planetSpec[downgradeTo].upgradeEnReqs[0] > obj.plEn + solarplus) or (Rules.planetSpec[downgradeTo].upgradeEnReqs[1] < obj.plEn + solarminus):
595
                # auto damage on plEn outside downgrade's upgrade range
596
                obj.plEnv -= Rules.envAutoMod
597
        if obj.plBio > Rules.planetSpec[obj.plType].maxBio:
598
            # auto damage on plBio > maxBio of class
599
            dEnv = int((obj.plBio - Rules.planetSpec[obj.plType].maxBio) * Rules.envAutoMod)
600
            if obj.plEnv > 0:
601
                obj.plEnv -= min(obj.plEnv, dEnv)
602
            else:
603
                obj.plEnv -= dEnv
604
            # small chance of self-upgrading
605
            spec = Rules.planetSpec[obj.plType]
606
            race = owner.race if owner else "H"
607
            chance = int((obj.plBio - spec.maxBio) * Rules.envSelfUpgradeChance[race])
608
            if Utils.rand(0, 10001) < chance and spec.upgradeTo and \
609
                    obj.plEn + solarplus >= spec.upgradeEnReqs[0] and \
610
                    obj.plEn + solarminus <= spec.upgradeEnReqs[1]:
611
                log.debug('IPlanet', obj.oid, 'Upgraded to', spec.upgradeTo)
612
                obj.plType = spec.upgradeTo
613
                Utils.sendMessage(tran, obj, Const.MSG_UPGRADED_PLANET_ECO, obj.oid, spec.upgradeTo)
614
        while obj.plEnv >= Rules.envInterval:
615
            #@log.debug('IPlanet', obj.oid, 'Env improved')
616
            obj.plEnv -= Rules.envInterval
617
            obj.changeEnv += Rules.envInterval
618
            if obj.plBio < 200: obj.plBio += 1
619
        while obj.plEnv < 0:
620
            if obj.plBio > 0:
621
                obj.plBio -= 1
622
                obj.plEnv += Rules.envInterval
623
                obj.changeEnv -= Rules.envInterval
624
            else:
625
                obj.changeEnv += obj.plEnv
626
                obj.plEnv = 0
627
        # downgrade planet if necessary
628
        if obj.plBio < Rules.planetSpec[obj.plType].minBio:
629
            downgradeTo = Rules.planetSpec[obj.plType].downgradeTo
630
            if downgradeTo:
631
                log.debug('IPlanet', obj.oid, 'Downgraded to', downgradeTo)
632
                obj.plType = downgradeTo
633
                Utils.sendMessage(tran, obj, Const.MSG_DOWNGRADED_PLANET_ECO, obj.oid, downgradeTo)
634
        # record changes
635
        obj.changeBio += obj.storBio
636
        obj.changeEn += obj.storEn
637
        obj.changePop += obj.storPop
638
        obj.changeEnv += obj.plEnv
639
640
    @public(Const.AL_ADMIN)
641
    def processPRODPhase(self, tran, obj, data):
642
        # max storage
643
        obj.maxPop = obj.plSlots * Rules.popPerSlot + Rules.popBaseStor
644
        obj.maxBio = obj.plSlots * Rules.bioPerSlot + Rules.bioBaseStor
645
        obj.maxEn = obj.plSlots * Rules.enPerSlot + Rules.enBaseStor
646
        # refuel & repair
647
        obj.refuelMax = 0
648
        obj.refuelInc = 0
649
        obj.repairShip = 0.0
650
        obj.upgradeShip = 0.0
651
        # train
652
        obj.trainShipInc = 0
653
        obj.trainShipMax = 0
654
        obj.fleetSpeedBoost = 1.0
655
        #
656
        if obj.storPop <= 0 and not obj.slots and obj.owner == Const.OID_NONE:
657
            # do not process this planet
658
            return
659
        obj.scannerPwr = Rules.scannerMinPwr
660
        obj.prodProd = Rules.basePlanetProdProd
661
        obj.prodSci = 0
662
        obj.changeBio = - obj.storBio
663
        obj.changeEn = - obj.storEn
664
        obj.changePop = - obj.storPop
665
        obj.changeEnv = - obj.plEnv
666
        obj.changeMorale = - obj.morale
667
        # parent objects
668
        system = tran.db[obj.compOf]
669
        galaxy = tran.db[system.compOf]
670
        # collect strategic resources
671
        owner = tran.db.get(obj.owner, None)
672
        if owner and obj.plStratRes != Const.SR_NONE:
673
            turn = tran.db[Const.OID_UNIVERSE].turn
674
            if turn % Rules.stratResRate == 0:
675
                owner.stratRes[obj.plStratRes] = owner.stratRes.get(obj.plStratRes, 0) + Rules.stratResAmountBig
676
                Utils.sendMessage(tran, obj, Const.MSG_EXTRACTED_STRATRES, obj.oid, obj.plStratRes)
677
        # compute base morale
678
        if owner:
679
            homePlanet = tran.db[owner.planets[0]]
680
            dist = int(math.sqrt((homePlanet.x - obj.x) ** 2 + (homePlanet.y - obj.y) ** 2))
681
            moraleTrgt = -37.5 * dist / owner.govPwrCtrlRange + 107.5
682
            obj.moraleModifiers[0] = max(Rules.minMoraleTrgt, min(moraleTrgt, Rules.maxMorale))
683
            #@log.debug(obj.oid, "Morale target", obj.moraleTrgt, "dist", dist, owner.govPwrCtrlRange)
684
        # auto regulation of min resources
685
        if obj.autoMinStor:
686
            obj.minBio = obj.minEn = 0
687
        # combat?
688
        isCombat = system.combatCounter > 0
689
        obj.unemployedPop = obj.storPop
690
        # ok, reset max pop
691
        obj.maxPop = 0
692
        # process all structures
693
        obj.maxShield = 0
694
        obj.solarmod = 0
695
        #@log.debug("Morale bonus/penalty for planet", obj.oid, moraleBonus)
696
        # reset of "morale modifier by buildings" value
697
        obj.moraleModifiers[1] = 0
698
        self._processStructs(tran, obj)
699
        if obj.revoltLen > 0 or isCombat:
700
            # no services available if distressed
701
            obj.refuelInc = obj.repairShip = obj.upgradeShip = obj.trainShipMax = obj.trainShipInc = 0
702
        # do shield self generation
703
        obj.prevShield = obj.shield #for planet display of shield growth
704
        if obj.maxShield < obj.shield:
705
            obj.shield = obj.maxShield
706
        if obj.maxShield > obj.shield and not isCombat:
707
            regenTemp = max(1, Rules.plShieldRegen* obj.maxShield) #always regen at at least 1
708
            obj.shield = min(obj.shield + regenTemp, obj.maxShield) #don't let it regen over shieldMax
709
        # pass scanner/... to the system
710
        obj.scannerPwr = min(obj.scannerPwr * (2.0 - galaxy.emrLevel), Rules.scannerMaxPwr)
711
        system.scannerPwrs[obj.owner] = max(obj.scannerPwr, system.scannerPwrs.get(obj.owner, 0))
712
713
        self._processPopulation(obj, owner)
714
        self._processProduction(tran, obj, owner)
715
        self._processEnvironmentChange(tran, obj, owner)
716
        # auto regulation of min resources
717
        if obj.autoMinStor:
718
            obj.minBio = min(obj.minBio, obj.maxBio / 2)
719
            obj.minEn = min(obj.minEn, obj.maxEn / 2)
720
        # science
721
        if owner:
722
            moraleBonus = Rules.moraleProdBonus[int(obj.morale / Rules.moraleProdStep)]
723
            obj.effProdSci = max(0, int(obj.prodSci * (owner.sciEff + moraleBonus)))
724
            owner.sciPoints += obj.effProdSci
725
        # planet with no population cannot have an owner
726
        # and planet with no owner cannot have population
727
        if (obj.storPop <= 0 and obj.owner != Const.OID_NONE) or obj.owner == Const.OID_NONE:
728
            self.cmd(obj).changeOwner(tran, obj, Const.OID_NONE, force = 1)
729
            obj.storPop = 0
730
731
    @public(Const.AL_ADMIN)
732
    def processACTIONPhase(self, tran, obj, data):
733
        return
734
735
    @public(Const.AL_ADMIN)
736
    def processFINALPhase(self, tran, obj, data):
737
        if obj.storPop <= 0 and not obj.slots and obj.owner == Const.OID_NONE:
738
            # do not process this planet
739
            return
740
        # reset of "morale modifier by population" value
741
        obj.moraleModifiers[2] = 0
742
        system = tran.db[obj.compOf]
743
        galaxy = tran.db[system.compOf]
744
        if galaxy.timeEnabled:
745
            owner = tran.db.get(obj.owner, None)
746
            # too much population affects morale (if there is more than base population)
747
            if obj.storPop > Rules.moraleBasePop:
748
                obj.moraleModifiers[2] -= Rules.moraleHighPopPenalty * obj.storPop / Rules.moraleBasePop
749
            elif obj.storPop <= Rules.moraleLowPop:
750
                obj.moraleModifiers[2] += Rules.moraleLowPopBonus
751
            else:
752
                # gradually removing LowPop bonus as we approach BasePop - big jumps are awful game
753
                # mechanic
754
                moraleBonusRange = Rules.moraleBasePop - Rules.moraleLowPop
755
                moraleBonus = float(obj.storPop - Rules.moraleLowPop) / moraleBonusRange
756
                obj.moraleModifiers[2] += int(Rules.moraleLowPopBonus * (1 - moraleBonus) )
757
            # there is effect of unemployed population
758
            # if there is none, there is a hit, if there is what's necessary, there is a bonus
759
            # effect between the two is linear
760
            idealUnemployedPop =  obj.plSlots * getattr(owner, "techLevel", 1) * Rules.tlPopReserve
761
            moraleBonusRange = Rules.unemployedMoraleHigh - Rules.unemployedMoraleLow
762
            unemployedRatio = min(1.0, float(obj.unemployedPop) / idealUnemployedPop)
763
            obj.moraleModifiers[3] = Rules.unemployedMoraleLow + int(moraleBonusRange * unemployedRatio)
764
            # count final morale values
765
            obj.moraleModifiers[4] = obj.moraleModifiers[0] +obj.moraleModifiers[1] + obj.moraleModifiers[2] + obj.moraleModifiers[3]
766
            obj.moraleTrgt = obj.moraleModifiers[4]
767
            obj.moraleTrgt = max(0.0, min(obj.moraleTrgt, Rules.maxMorale))
768
            if obj.morale > int(obj.moraleTrgt):
769
                obj.morale -= max(1.0, (obj.morale - obj.moraleTrgt) * Rules.moraleChngPerc)
770
            elif obj.morale < int(obj.moraleTrgt) and system.combatCounter == 0:
771
                obj.morale += max(1.0, (obj.moraleTrgt - obj.morale) * Rules.moraleChngPerc)
772
            #@log.debug('IPlanet', 'Mor Mor trgt/reb thr', obj.morale, obj.moraleTrgt)
773
            # revolt?
774
            if obj.revoltLen > 0:
775
                obj.revoltLen += 1
776
            if obj.morale < Rules.revoltThr and obj.owner != Const.OID_NONE and obj.revoltLen == 0:
777
                chance = (Rules.revoltThr - obj.morale) * Rules.moralePerPointChance
778
                #@log.debug('IPlanet', 'Start revolt? mor, mor trgt, reb thr, chance', obj.morale, obj.moraleTrgt, chance)
779
                if  Utils.rand(0, 101) <= chance:
780
                    # rebelion starts
781
                    #@log.debug('IPlanet', 'Revolt on', obj.oid)
782
                    obj.revoltLen = 1
783
                    Utils.sendMessage(tran, obj, Const.MSG_REVOLT_STARTED, obj.oid, None)
784
            elif obj.revoltLen > 0 and obj.morale > Rules.revoltThr:
785
                chance = (obj.morale - Rules.revoltThr) * Rules.moralePerPointChance
786
                #@log.debug('IPlanet', 'Stop revolt? mor, mor trgt, reb thr, chance', obj.morale, obj.moraleTrgt, chance)
787
                if Utils.rand(0, 101) <= chance:
788
                    # revolt ends
789
                    obj.revoltLen = 0
790
                    Utils.sendMessage(tran, obj, Const.MSG_REVOLT_ENDED, obj.oid, None)
791
            obj.morale = max(0.0, min(Rules.maxMorale, obj.morale))
792
            obj.changeMorale += obj.morale
793
            # when rebelling destroy some resources
794
            if obj.revoltLen > 0:
795
                obj.storBio -= int(obj.storBio * Rules.revoltDestrBio)
796
                obj.storEn -= int(obj.storEn * Rules.revoltDestrEn)
797
            # storage
798
            obj.storBio = min(obj.storBio, obj.maxBio)
799
            obj.storEn = min(obj.storEn, obj.maxEn)
800
        # collect stats
801
        if obj.owner != Const.OID_NONE:
802
            player = tran.db[obj.owner]
803
            player.stats.storPop += obj.storPop
804
            player.stats.prodProd += obj.prodProd
805
            player.stats.effProdProd += obj.effProdProd
806
            player.stats.prodSci += obj.prodSci
807
            player.stats.effProdSci += obj.effProdSci
808
            player.stats.structs += len(obj.slots)
809
            player.stats.slots += obj.plSlots
810
            # morale computation
811
            homePlanet = tran.db[player.planets[0]]
812
            dist = int(math.sqrt((homePlanet.x - obj.x) ** 2 + (homePlanet.y - obj.y) ** 2))
813
            player.tmpPopDistr[dist] = player.tmpPopDistr.get(dist, 0) + obj.storPop
814
815
    def getScanInfos(self, tran, obj, scanPwr, player):
816
        if scanPwr >= Rules.level1InfoScanPwr:
817
            result = IDataHolder()
818
            result._type = Const.T_SCAN
819
            result.scanPwr = scanPwr
820
            result.oid = obj.oid
821
            result.signature = obj.signature
822
            result.type = obj.type
823
            result.orbit = obj.orbit
824
            result.compOf = obj.compOf
825
            result.x = obj.x
826
            result.y = obj.y
827
            result.plType = obj.plType
828
        if scanPwr >= Rules.level2InfoScanPwr:
829
            result.plDiameter = obj.plDiameter
0 ignored issues
show
introduced by
The variable result does not seem to be defined in case scanPwr >= Rules.level1InfoScanPwr on line 816 is False. Are you sure this can never be the case?
Loading history...
830
            if getattr(obj, "plType", 'X') != 'G':
831
                result.plMin = obj.plMin
832
            result.plBio = obj.plBio
833
            result.plEn = obj.plEn
834
            result.plSlots = obj.plSlots
835
            result.plStratRes = obj.plStratRes
836
            result.plMaxSlots = obj.plMaxSlots
837
        if scanPwr >= Rules.level3InfoScanPwr:
838
            result.name = obj.name
839
            result.storPop = obj.storPop
840
            result.owner = obj.owner
841
        if scanPwr >= Rules.level4InfoScanPwr:
842
            # TODO provide less information
843
            result.hasRefuel = (obj.refuelInc > 0) #simple detect if docks exist for problems dialog
844
            result.slots = obj.slots
845
            result.shield = obj.shield
846
            result.prevShield = -1
847
            result.maxShield = -1
848
        if scanPwr >= Rules.partnerScanPwr:
849
            result.maxShield = obj.maxShield
850
            result.prevShield = obj.prevShield
851
            result.refuelMax = obj.refuelMax
852
            result.refuelInc = obj.refuelInc
853
            result.scannerPwr = obj.scannerPwr
854
            result.trainShipInc = obj.trainShipInc
855
            result.trainShipMax = obj.trainShipMax
856
            result.upgradeShip = obj.upgradeShip
857
            result.repairShip = obj.repairShip
858
            result.fleetSpeedBoost = obj.fleetSpeedBoost
859
        return [result]
860
861
    def loadDOMNode(self, tran, obj, xoff, yoff, orbit, node):
862
        obj.x = xoff
863
        obj.y = yoff
864
        obj.orbit = orbit
865
        for elem in node.childNodes:
866
            if elem.nodeType == Node.ELEMENT_NODE:
867
                name = elem.tagName
868
                if name == 'properties':
869
                    self.loadDOMAttrs(obj, elem)
870
                elif name == 'startingpoint':
871
                    galaxy = tran.db[tran.db[obj.compOf].compOf]
872
                    galaxy.startingPos.append(obj.oid)
873
                    galaxy.numOfStartPos += 1
874
                else:
875
                    raise ige.GameException('Unknown element %s' % name)
876
        return Const.SUCC
877
878
    def update(self, tran, obj):
879
        # clean up negative build queues and fix missing demolishStruct keys
880
        loopAgain = True
881
882
        while loopAgain:
883
            deletedKey = False
884
            for key in range(0,len(obj.prodQueue)):
885
                item = obj.prodQueue[key]
886
                if not hasattr(item, "demolishStruct"):
887
                    item.demolishStruct = Const.OID_NONE
888
                if item.quantity < 0:
889
                    log.warning("Deleting negative item queue on", obj.oid,"for player",obj.owner)
890
                    if item.isShip:
891
                        tech = player.shipDesigns[item.techID]
0 ignored issues
show
introduced by
The variable player does not seem to be defined for all execution paths.
Loading history...
892
                    else:
893
                        tech = Rules.techs[item.techID]
894
                    player = tran.db[obj.owner]
895
                    for sr in tech.buildSRes:
896
                        player.stratRes[sr] = player.stratRes.get(sr, 0) + item.quantity #quantity negative, so subtracting strat resources
897
                    # del the bad item. Since this changes indicies, start the check over again on remaining items
898
                    deletedKey = True
899
                    del obj.prodQueue[key]
900
                    break
901
            # no more bad entries found; break the while loop
902
            if not deletedKey:
903
                loopAgain = False
904
905
        # change owner to Const.OID_NONE when owner is invalid
906
        if obj.owner != Const.OID_NONE:
907
            player = tran.db.get(obj.owner, None)
908
            if not player or player.type not in Const.PLAYER_TYPES or obj.oid not in player.planets:
909
                # TODO this can be a probem - this planet cannot be attacked!
910
                log.warning("Changing owner to Const.OID_NONE - invalid owner", obj)
911
                self.cmd(obj).changeOwner(tran, obj, Const.OID_NONE, force = 1)
912
                # kill all population
913
                obj.storPop = 0
914
                return
915
        # check compOf
916
        if not tran.db.has_key(obj.compOf) or tran.db[obj.compOf].type != Const.T_SYSTEM:
917
            log.debug("CONSISTENCY invalid compOf for planet", obj.oid)
918
        # fix signature
919
        obj.signature = 75
920
921
    @public(Const.AL_FULL)
922
    def changePlanetsGlobalQueue(self, tran, obj, newQueue):
923
        player = tran.db[obj.owner]
924
        if newQueue < 0 or newQueue >= len(player.prodQueues):
925
            raise ige.GameException("Invalid queue")
926
        obj.globalQueue = newQueue
927
        return obj.globalQueue
928
929
    def popGlobalQueue(self, tran, obj):
930
        player = tran.db[obj.owner]
931
        queue = obj.globalQueue
932
        task = None
933
        if len(player.prodQueues[queue]):
934
            task = copy.copy(player.prodQueues[queue][0])
935
            if task.quantity > 1:
936
                player.prodQueues[queue][0].quantity -= 1
937
            else:
938
                if task.reportFin:
939
                    Utils.sendMessage(tran, obj, Const.MSG_QUEUE_TASK_ALLOTED, Const.OID_NONE, (queue, task.techID))
940
                del player.prodQueues[queue][0]
941
            # add other demanded values, report finalization was used to report allot (to prevent reporting every unit)
942
            task.reportFin = 0
943
            task.quantity = 1
944
            task.isShip = task.techID < 1000
945
            task.targetID = obj.oid
946
            task.currProd = 0
947
            task.demolishStruct = Const.OID_NONE
948
        return task
949
950
    def deleteDesign(self, tran, obj, designID, keepWIP = 0):
951
        # TODO: handle stategic resources
952
        for task in obj.prodQueue[:]:
953
            if task.isShip and task.techID == designID:
954
                if task.currProd > 0 and keepWIP:
955
                    self.cmd(obj).changeConstruction(tran, obj, obj.procQueue.index(task), 1)
956
                else:
957
                    self.cmd(obj).abortConstruction(tran, obj, obj.prodQueue.index(task))
958
959
    def changeShipDesign(self, tran, obj, oldDesignID, newDesignID):
960
        # TODO: handle strategic resources
961
        for task in obj.prodQueue[:]:
962
            if task.isShip and task.techID == oldDesignID:
963
                task.techID = newDesignID
964
                task.currProd = int(task.currProd / Rules.shipUpgradeMod)
965
966
    ##
967
    ## Combat related functions
968
    ##
969
970
    def getPreCombatData(self, tran, obj):
971
        # scan buildings and fire their weapons
972
        shots = {0: [], 1: [], 2: [], 3: []}
973
        if obj.owner == Const.OID_NONE:
974
            return shots, [0, 0, 0, 8], False
975
        player = tran.db[obj.owner]
976
        system = tran.db[obj.compOf]
977
        desCount = {}
978
        firing = False
979
        systemAtt = 0;
980
        systemDef = 0;
981
        for struct in obj.slots:
982
            structTechID = struct[Const.STRUCT_IDX_TECHID]
983
            opStatus = struct[Const.STRUCT_IDX_OPSTATUS] / 100.0
984
            tech = Rules.techs[structTechID]
985
            desCount[structTechID] = desCount.get(structTechID, 0) + 1
986
            wpnCount = {}
987
            if not tech.structWeapons:
988
                continue
989
            firing = True
990
            for cClass in range(0, 4):
991
                weaponID = player.planetWeapons[cClass]
992
                if weaponID is None:
993
                    continue
994
                weapon = Rules.techs[weaponID]
995
                maxWeaponCount = int(tech.structWeapons[cClass] * opStatus)
996
                for weaponIdx in range(0, maxWeaponCount):
997
                    #@log.debug(obj.oid, "FIRING PLANET WEAPON", weapon.name)
998
                    wpnCount[weaponID] = wpnCount.get(weaponID, 0) + 1
999
                    #
1000
                    weaponEff = Rules.techImprEff[player.techs.get(weaponID, Rules.techBaseImprovement)]
1001
                    # base attack
1002
                    attack = tech.combatAtt + int(weapon.weaponAtt * weaponEff)
1003
                    # because ALL counters starts at 1, subtract 3
1004
                    count = system.combatCounter + desCount[structTechID] + wpnCount[weaponID] - 2
1005
                    # add to attacks
1006
                    #@log.debug('IPlanet', obj.oid, structTechID, "Count", count, 'Shots', weapon.name, ShipUtils.getRounds(weapon.weaponROF, count))
1007
                    for round in xrange(0, ShipUtils.getRounds(weapon.weaponROF, count)):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
1008
                        shots[weapon.weaponClass].append((attack, weaponID))
1009
        # hit limit
1010
        obj.maxHits = len(obj.slots)
1011
        obj.hitCounter = 0
1012
        obj.lastHitClass = 3
1013
        obj.hitMod = 1.0
1014
        log.debug(obj.oid, "Combat settings", obj.maxHits)
1015
        # +1 means population only hit
1016
        return shots, [0, 0, 0, 8], firing
1017
1018
    def applyShot(self, tran, obj, defense, attack, weaponID, cClass, count):
1019
        #@log.debug('IPlanet', 'Apply shot', weaponID, attack, cClass, count)
1020
        # compute chance to hit
1021
        weapon = Rules.techs[weaponID]
1022
        #system defense bonus is dropped for planets...structures can't move; just calculate defense off structure defense
1023
        defense = Rules.combatStructDefense
1024
        destroyed = 0
1025
        dmg = 0
1026
        # limit number of shots
1027
        if weapon.weaponClass < obj.lastHitClass:
1028
            #@log.debug(obj.oid, "Different class", obj.lastHitClass, weapon.weaponClass, obj.maxHits)
1029
            obj.maxHits = int(Rules.combatHitXferMod * obj.maxHits * (obj.lastHitClass - weapon.weaponClass))
1030
            obj.hitCounter = int(Rules.combatHitXferMod * obj.hitCounter * (obj.lastHitClass - weapon.weaponClass))
1031
            obj.lastHitClass = weapon.weaponClass
1032
        if weapon.weaponROF > 1:
1033
            #@log.debug(obj.oid, "Increasing counter PL", 1.0 / weapon.weaponROF)
1034
            obj.hitCounter += 1.0 / weapon.weaponROF
1035
        else:
1036
            #@log.debug(obj.oid, "Increasing counter PL", 1)
1037
            obj.hitCounter += 1
1038
        if obj.hitCounter > obj.maxHits:
1039
            obj.hitCounter = 0
1040
            obj.hitMod *= Rules.combatStructureHitMod
1041
            #@log.debug(obj.oid, "Increasing hit penalty", obj.hitMod, obj.maxHits)
1042
        attackChance = obj.hitMod * attack / (attack + defense)
1043
        #@log.debug(obj.oid, "Chance to attack", attackChance, obj.hitMod, obj.hitCounter, obj.maxHits,
1044
        #@    "without penalty:", float(attack) / (attack + defense))
1045
        #@log.debug('IPlanet', obj.oid, 'HIT?', attack + defense + 1, defense)
1046
        absorb = 0 #for when it doesn't hit
1047
        if random.random() <= attackChance:
1048
            # hit
1049
            player = tran.db[obj.owner]
1050
            weaponEff = Rules.techImprEff[player.techs.get(weaponID, Rules.techBaseImprovement)]
1051
            dmg = ShipUtils.computeDamage(weapon.weaponClass, 3, weapon.weaponDmgMin, weapon.weaponDmgMax, weaponEff)
1052
            #@log.debug(obj.oid, 'HIT! att=%d vs def=%d, dmg=%d '% (attack, defense, dmg))
1053
            #shield strike
1054
            if obj.shield > 0:
1055
                absorb = min(dmg,obj.shield)
1056
                obj.shield -= absorb
1057
                dmg -= absorb
1058
            if dmg == 0:
1059
                return 0+absorb, 0, 3
1060
            # select slot
1061
            if count == 7 or not obj.slots:
1062
                #@log.debug('IPlanet', 'Population hit')
1063
                # population hit
1064
                if obj.storPop == 0:
1065
                    dmg = 0
1066
                else:
1067
                    # free slot hit -> dmg population
1068
                    # OLD dmgPop = int(Rules.popPerSlot * float(dmg) / Rules.popSlotHP * Rules.popKillMod)
1069
                    dmgPop = int(dmg * Rules.popSlotKillMod)
1070
                    obj.storPop = max(obj.storPop - dmgPop, 0)
1071
                    obj.changePop -= dmgPop
1072
                    if obj.storPop > 0:
1073
                        obj.morale -= Rules.moraleModPlHit * float(dmgPop) / float(obj.storPop)
1074
                    #@log.debug('IPlanet', obj.oid, 'Morale penalty', dmg, maxHP, Rules.moraleModPlHit * float(dmg) / float(maxHP))
1075
            elif count < 0:
1076
                # TODO can be count negative?
1077
                log.warning('IPlanet', 'applyShot: count is negative')
1078
            else:
1079
                if count == 6:
1080
                    # random structure hit
1081
                    #@log.debug('IPlanet', 'Random structure hit')
1082
                    struct = obj.slots[Utils.rand(0, len(obj.slots))]
1083
                else:
1084
                    # most damaged structure hit
1085
                    #@log.debug('IPlanet', 'Most damaged structure hit')
1086
                    struct = obj.slots[-1]
1087
                    for tmpStruct in obj.slots:
1088
                        if tmpStruct[Const.STRUCT_IDX_HP] <= struct[Const.STRUCT_IDX_HP]:
1089
                            struct = tmpStruct
1090
                # compute sum hp of all buildings
1091
                sumHP = 0
1092
                for tmpStruct in obj.slots:
1093
                    sumHP += tmpStruct[Const.STRUCT_IDX_HP]
1094
                # damage building
1095
                struct[Const.STRUCT_IDX_HP] -= dmg
1096
                # "damage" population
1097
                tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
1098
                # compute struct effectivity
1099
                techEff = Utils.getTechEff(tran, struct[Const.STRUCT_IDX_TECHID], obj.owner)
1100
                maxHP = int(tech.maxHP * techEff)
1101
                dmgPop = int(tech.operWorkers * float(dmg) / maxHP * Rules.popKillMod)
1102
                obj.storPop = max(obj.storPop - dmgPop, 0)
1103
                obj.changePop -= dmgPop
1104
                # destroy building
1105
                if struct[Const.STRUCT_IDX_HP] <= 0:
1106
                    destroyed = 1
1107
                    dmg += struct[Const.STRUCT_IDX_HP]
1108
                    obj.slots.remove(struct)
1109
                # compute morale penalty
1110
                if dmg:
1111
                    obj.morale -= Rules.moraleModPlHit * float(dmg) / float(sumHP)
1112
                    #@log.debug('IPlanet', obj.oid, 'Morale penalty', dmg, sumHP, Rules.moraleModPlHit * float(dmg) / float(sumHP))
1113
        #@log.debug('IPlanet', 'Shot applied', dmg, destroyed)
1114
        # when destroyed, only class 3 (structure) i valid
1115
        return dmg+absorb, destroyed, 3
1116
1117
    def distributeExp(self, tran, obj):
1118
        # TODO - will buildings have exp? Answ: NO
1119
        if hasattr(obj, "maxHits"):
1120
            del obj.maxHits
1121
            del obj.hitCounter
1122
            del obj.lastHitClass
1123
            del obj.hitMod
1124
1125
    def surrenderTo(self, tran, obj, newOwnerID):
1126
        # morale is lost when this is called
1127
        obj.morale -= Rules.moraleLostWhenSurrender
1128
        if obj.morale >= Rules.revoltThr:
1129
            #@log.debug('IPlanet', 'Surrender - revolt thr not reached', obj.morale)
1130
            return 0
1131
        chance = (Rules.revoltThr - obj.morale) * Rules.moralePerPointChance
1132
        #@log.debug('IPlanet', 'Surrender? mor, mor trgt, reb thr, chance', obj.morale, obj.moraleTrgt, chance)
1133
        if Utils.rand(0, 101) > chance:
1134
            # do not surrender!
1135
            #@log.debug('IPlanet', 'Surrender - pure luck', obj.morale, obj.revoltLen)
1136
            return 0
1137
        # we've lost the battle - we have a new owner
1138
        #@log.debug('IPlanet', 'Surrender - surrending to', newOwnerID)
1139
        newOwner = tran.db[newOwnerID]
1140
        if newOwner.type == Const.T_PIRPLAYER or newOwner.type == Const.T_AIPIRPLAYER:
1141
            # special handling for pirates
1142
            currentTurn = tran.db[Const.OID_UNIVERSE].turn
1143
            # prevent abuse - require 8 turns between capturing the same planet and require the owner to control the planet at least 2 turns if you want to gain fame & tech (two turns prevents orbiting pirate fleet from immediately bombing)
1144
            if (currentTurn - obj.lastPirCapture) > 8 and (currentTurn - obj.ownerSince) > 2:
1145
                # gain/lose fame
1146
                self.cmd(newOwner).capturePlanet(tran, newOwner, obj)
1147
                # steal ship techs
1148
                self.cmd(newOwner).stealTechs(tran, newOwner, obj.owner, obj.oid)
1149
            else:
1150
                log.debug(obj.oid, "Pirate captured planet too soon after previous capture or colonization to gain bonuses", obj.oid)
1151
            obj.storPop = 0
1152
            obj.lastPirCapture = currentTurn
1153
            self.cmd(obj).changeOwner(tran, obj, Const.OID_NONE, force = 1)
1154
        else:
1155
            # change owner
1156
            self.cmd(obj).changeOwner(tran, obj, newOwnerID, force = 1)
1157
        # blow up all military buildings
1158
        for struct in obj.slots[:]:
1159
            tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
1160
            if tech.isMilitary:
1161
                obj.slots.remove(struct)
1162
        return 1
1163