Passed
Pull Request — master (#192)
by Marek
01:52
created

ige.ospace.IPlanet.IPlanet._processStructs()   B

Complexity

Conditions 6

Size

Total Lines 63
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 43
nop 3
dl 0
loc 63
rs 7.9146
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 = techOper - techProd * prodMod
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
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, opStatuses, maxHP):
327
        # auto repair/damage
328
        # also damage structures on not owned planets
329
        opStatusHP, opStatusOther = opStatuses
330
        properHP = opStatusOther * maxHP
331
        repairDiff = min(properHP - struct[Const.STRUCT_IDX_HP], Rules.repairRunningRatio * maxHP)
332
        decayDiff = min(struct[Const.STRUCT_IDX_HP] - properHP, Rules.decayRatio * maxHP)
333
        if struct[Const.STRUCT_IDX_HP] < properHP:
334
            struct[Const.STRUCT_IDX_HP] += repairDiff
335
            struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_REPAIRING
336
        elif struct[Const.STRUCT_IDX_HP] > properHP:
337
            # flag only for non functional structure
338
            struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_DETER
339
            # damage it a bit
340
            struct[Const.STRUCT_IDX_HP] -= decayDiff
341
            if obj.storPop > 0:
342
                # do not fall below 1 HP for populated planets so it won't destroy buildings
343
                struct[Const.STRUCT_IDX_HP] = max(struct[Const.STRUCT_IDX_HP], 1)
344
        if struct[Const.STRUCT_IDX_HP] <= 0:
345
            obj.slots.remove(struct)
346
347
    def _processStructs(self, tran, obj):
348
        for struct in obj.slots[:]:
349
            # skip structure if it was built this turn
350
            if struct[Const.STRUCT_IDX_STATUS] & Const.STRUCT_STATUS_NEW:
351
                continue
352
            tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
353
            # compute struct effectivity
354
            techEff = Utils.getTechEff(tran, struct[Const.STRUCT_IDX_TECHID], obj.owner)
355
            # morale does not affect hit points of structures
356
            maxHP = int(tech.maxHP * techEff)
357
            # auto regulation of min resources
358
            if obj.autoMinStor:
359
                obj.minBio += tech.operBio * Rules.autoMinStorTurns
360
                obj.minEn += tech.operEn * Rules.autoMinStorTurns
361
            # produce/consume resources
362
            opStatuses = self._getStructStatus(obj, struct, tech, maxHP)
363
            self._updateStructHP(obj, struct, opStatuses, maxHP)
364
            opStatus = min(opStatuses)
365
            # solarmod effects ENV change and terraforming only if benificial
366
            if tech.solarMod * opStatus > 0:
367
                obj.solarmod = max(obj.solarmod, tech.solarMod * techEff * opStatus)
368
            elif tech.solarMod * opStatus < 0:
369
                obj.solarmod = min(obj.solarmod, tech.solarMod * techEff * opStatus)
370
            # bio
371
            prodMod = self._getStructProdMod(obj, tech.prodBioMod)
372
            obj.storBio += int(tech.prodBio * prodMod * techEff * opStatus) - int(tech.operBio * opStatus)
373
            # en
374
            prodMod = self._getStructProdMod(obj, tech.prodEnMod)
375
            obj.storEn += int(tech.prodEn * prodMod * techEff * opStatus) - int(tech.operEn * opStatus)
376
377
            obj.unemployedPop -= min(obj.unemployedPop, int(tech.operWorkers * opStatus))
378
            obj.storPop += int(tech.prodPop * techEff * opStatus)
379
            obj.scannerPwr = max(int(tech.scannerPwr * techEff * opStatus), obj.scannerPwr)
380
            obj.scannerPwr = min(obj.scannerPwr, Rules.scannerMaxPwr)
381
            # rebellion and combat has common penalty
382
            prodMod = self._getStructProdMod(obj, tech.prodProdMod)
383
            obj.prodProd += int(tech.prodProd * prodMod * techEff * opStatus)
384
            # science
385
            prodMod = self._getStructProdMod(obj, tech.prodSciMod)
386
            obj.prodSci += int(tech.prodSci * prodMod * techEff * opStatus)
387
            # refuelling & repairing
388
            obj.refuelMax = max(obj.refuelMax, int(tech.refuelMax * techEff * opStatus))
389
            # refuelling
390
            obj.refuelInc = max(obj.refuelInc, int(tech.refuelInc * techEff * opStatus))
391
            # repair
392
            obj.repairShip += tech.repairShip * techEff * opStatus
393
            obj.upgradeShip += tech.upgradeShip * techEff * opStatus
394
            # train
395
            obj.trainShipMax = max(obj.trainShipMax, tech.trainShipMax)
396
            obj.trainShipInc = max(obj.trainShipInc, tech.trainShipInc * techEff * opStatus)
397
            # shielding
398
            obj.maxShield = max(tech.planetShield * techEff * opStatus, obj.maxShield)
399
            # stargates
400
            obj.fleetSpeedBoost = max(obj.fleetSpeedBoost, tech.fleetSpeedBoost * techEff * opStatus)
401
            # storage
402
            obj.maxBio += int(tech.storBio * techEff)
403
            obj.maxEn += int(tech.storEn * techEff)
404
            # each structure accomodate it's workers
405
            obj.maxPop += tech.operWorkers
406
            obj.maxPop += int(tech.storPop * techEff)
407
            obj.plEnv += int(tech.prodEnv * techEff * opStatus)
408
            # morale modifier of the building
409
            obj.moraleModifiers[1] += tech.moraleTrgt * techEff * opStatus
410
411
    def _processPopulation(self, obj, owner):
412
        if not obj.storPop:
413
            return
414
        # population reserve
415
        obj.maxPop += obj.plSlots * getattr(owner, "techLevel", 1) * Rules.tlPopReserve
416
        # max pop
417
        maxPop = obj.maxPop
418
        if obj.popEatBio: maxPop = min(maxPop,  1000.0 * obj.storBio / obj.popEatBio)
419
        if obj.popEatEn: maxPop = min(maxPop, 1000.0 * obj.storEn / obj.popEatEn)
420
        maxPop = int(maxPop)
421
        # eat
422
        pop = obj.storPop / 1000.0
423
        wantBio = int(math.ceil(pop * obj.popEatBio))
424
        wantEn = int(math.ceil(pop * obj.popEatEn))
425
        # auto regulation of min resources
426
        if obj.autoMinStor:
427
            obj.minBio += wantBio * Rules.autoMinStorTurns
428
            obj.minEn += wantEn * Rules.autoMinStorTurns
429
        # consume resources
430
        obj.storBio -= min(obj.storBio, wantBio)
431
        obj.storEn -= min(obj.storEn, wantEn)
432
        # modify pop
433
        if obj.storPop > maxPop:
434
            # die
435
            obj.storPop -= max(int((obj.storPop - maxPop) * Rules.popDieRate), Rules.popMinDieRate)
436
            #if obj.storPop < maxPop: obj.storPop = maxPop
437
            # do not generate this message when construction has been destroyed
438
            # and do not lower morale too
439
            if obj.storPop < obj.maxPop:
440
                obj.morale = max(obj.morale - Rules.moraleLostNoFood,0)
441
        elif obj.storPop < maxPop:
442
            # born
443
            obj.storPop += max(min(int(obj.storPop * Rules.popGrowthRate), maxPop - obj.storPop), Rules.popMinGrowthRate)
444
445
    def _buildShip(self, tran, obj, item, owner):
446
        system = tran.db[obj.compOf]
447
        # find commander's fleet
448
        fleet = None
449
        # check if current system has any redirection
450
        hasRedirection = obj.compOf in owner.shipRedirections
451
        for fleetID in system.fleets:
452
            tmpFleet = tran.db[fleetID]
453
            if tmpFleet.owner == obj.owner and Utils.isIdleFleet(tmpFleet):
454
                fleet = tmpFleet
455
                break
456
        if not fleet or hasRedirection:
457
            fleet = self.new(Const.T_FLEET)
458
            tran.db.create(fleet)
459
            self.cmd(fleet).create(tran, fleet, system, obj.owner)
460
            self.cmd(fleet).addAction(tran, fleet, 0, Const.FLACTION_REDIRECT, Const.OID_NONE, None)
461
        # add ships to the fleet
462
        self.cmd(fleet).addNewShip(tran, fleet, item.techID)
463
        if item.reportFin and item.quantity == 1:
464
            Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_SHIP, obj.oid, item.techID)
465
466
    def _buildStructure(self, tran, obj, item, tech, target):
467
        # if there is struct to demolish, find it and delete it
468
        if item.demolishStruct != Const.OID_NONE:
469
            structToDemolish = None
470
            for struct in target.slots:
471
                if struct[Const.STRUCT_IDX_TECHID] == item.demolishStruct:
472
                    structToDemolish = struct
473
                    break
474
            if structToDemolish:
475
                # struct found -- delete it
476
                target.slots.remove(structToDemolish)
477
            else:
478
                # well, this can be a problem?
479
                # shall we report it? (TODO: decide)
480
                pass
481
        if len(target.slots) < target.plSlots:
482
            target.slots.append(Utils.newStructure(tran, item.techID, obj.owner))
483
            try:
484
                tech.finishConstrHandler(tran, obj, target, tech)
485
            except Exception:
486
                log.warning("Cannot execute finish constr handler")
487
            if item.reportFin and item.quantity == 1:
488
                Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_STRUCTURE, target.oid, item.techID)
489
        else:
490
            # no free slot!
491
            Utils.sendMessage(tran, obj, Const.MSG_CANNOTBUILD_NOSLOT, target.oid, None)
492
493
    def _processProduction(self, tran, obj, owner):
494
        # produce items in construction queue
495
        if owner:
496
            moraleBonus = Rules.moraleProdBonus[int(obj.morale / Rules.moraleProdStep)]
497
            prod  = obj.effProdProd = max(0, int(obj.prodProd * (owner.prodEff + moraleBonus)))
498
            if (obj.morale > 15 and prod == 0 and obj.prodProd > 0 and owner.prodEff > 0): #added for super-low moral bonus issues
499
                prod  = obj.effProdProd = 1
500
        else:
501
            prod = obj.prodProd
502
        explicitIdleProd = 0.0
503
        # empty queue should be filled by global queue
504
        if len(obj.prodQueue) == 0 and prod:
505
            task = self.cmd(obj).popGlobalQueue(tran, obj)
506
            if task:
507
                obj.prodQueue.append(task)
508
        index = 0
509
        while prod > 0 and index < len(obj.prodQueue):
510
            item = obj.prodQueue[index]
511
            # check if owner has this tech
512
            if not item.isShip and item.techID not in owner.techs:
513
                # bad tech
514
                del obj.prodQueue[index]
515
                # TODO send message
516
            # set target
517
            target = tran.db[item.targetID]
518
            # set tech and build conditions
519
            if item.isShip:
520
                tech = tran.db[obj.owner].shipDesigns[item.techID]
521
                mod = Rules.buildOnSamePlanetMod
522
            else:
523
                tech = Rules.techs[item.techID]
524
                # check validity of the project
525
                if not tech.validateConstrHandler(tran, obj, target, tech):
526
                    index += 1
527
                    # message to player
528
                    Utils.sendMessage(tran, obj, Const.MSG_INVALID_TASK, obj.oid, item.techID)
529
                    continue
530
                # building on other planet is more expensive
531
                if item.targetID == obj.oid:
532
                    mod = Rules.buildOnSamePlanetMod
533
                else:
534
                    mod = Rules.buildOnAnotherPlanetMod
535
            # compute needs (do not consume resources under minimal storage)
536
            wantProd = min(int(tech.buildProd * mod / tech.buildTurns - item.currProd), prod)
537
            # production
538
            item.changePerc = wantProd * 10000 / (tech.buildProd * mod)
539
            # consume / produce
540
            if item.techID == Rules.Tech.IDLETASK and item.isShip == 0:
541
                explicitIdleProd += wantProd
542
            prod -= wantProd
543
            item.currProd += wantProd
544
            # check, if production is complete
545
            if item.currProd >= tech.buildProd * mod:
546
                # item is complete
547
                if item.isShip:
548
                    self._buildShip(tran, obj, item, owner)
549
                elif tech.isStructure:
550
                    self._buildStructure(tran, obj, item, tech, target)
551
                elif tech.isProject:
552
                    tech.finishConstrHandler(tran, obj, target, tech)
553
                    if item.reportFin and item.quantity == 1:
554
                        Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_PROJECT, target.oid, item.techID)
555
                else:
556
                    raise ige.GameException('Unsupported type of technology %d ' % item.techID)
557
                # remove item from prod queue
558
                item.quantity -= 1
559
                if item.quantity == 0:
560
                    # remove item from the queue
561
                    del obj.prodQueue[index]
562
                    # was it last item in the queue? pop the global one!
563
                    if index == len(obj.prodQueue):
564
                        task = self.cmd(obj).popGlobalQueue(tran, obj)
565
                        if task:
566
                            obj.prodQueue.append(task)
567
568
                else:
569
                    # try to produce another item
570
                    item.currProd = 0
571
            else:
572
                # item is not complete stop production
573
                index += 1
574
                break
575
        # decay items not currently produced
576
        while index < len(obj.prodQueue):
577
            item = obj.prodQueue[index]
578
            item.currProd = max(0, int(item.currProd - max(item.currProd * Rules.decayRatio, 1)))
579
            index += 1
580
        # use excess raw CP to increase production elsewhere
581
        prod += explicitIdleProd
582
        if prod > 0.0:
583
            owner.prodIncreasePool += prod
584
585
    def _processEnvironmentChange(self, tran, obj):
586
        downgradeTo = Rules.planetSpec[obj.plType].downgradeTo
587
        solarminus = 0
588
        solarplus = 0
589
        if obj.solarmod > 0:
590
            solarplus = obj.solarmod
591
        if obj.solarmod < 0:
592
            solarminus = 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     #    @log.debug('IPlanet', obj.oid, 'Env auto damage', obj.plType, obj.plBio, Rules.planetSpec[obj.plType].maxBio)
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
            if owner:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable owner does not seem to be defined.
Loading history...
607
                chance = int((obj.plBio - spec.maxBio) * Rules.envSelfUpgradeChance[owner.race])
608
            else:
609
                chance = int((obj.plBio - spec.maxBio) * Rules.envSelfUpgradeChance["H"])
610
            if Utils.rand(0, 10001) < chance and spec.upgradeTo and \
611
                obj.plEn + solarplus >= spec.upgradeEnReqs[0] and obj.plEn + solarminus <= spec.upgradeEnReqs[1]:
612
                log.debug('IPlanet', obj.oid, 'Upgraded to', spec.upgradeTo)
613
                obj.plType = spec.upgradeTo
614
                Utils.sendMessage(tran, obj, Const.MSG_UPGRADED_PLANET_ECO, obj.oid, spec.upgradeTo)
615
        while obj.plEnv >= Rules.envInterval:
616
            #@log.debug('IPlanet', obj.oid, 'Env improved')
617
            obj.plEnv -= Rules.envInterval
618
            obj.changeEnv += Rules.envInterval
619
            if obj.plBio < 200: obj.plBio += 1
620
        while obj.plEnv < 0:
621
            if obj.plBio > 0:
622
                obj.plBio -= 1
623
                obj.plEnv += Rules.envInterval
624
                obj.changeEnv -= Rules.envInterval
625
            else:
626
                obj.changeEnv += obj.plEnv
627
                obj.plEnv = 0
628
        # downgrade planet if necessary
629
        if obj.plBio < Rules.planetSpec[obj.plType].minBio:
630
            downgradeTo = Rules.planetSpec[obj.plType].downgradeTo
631
            if downgradeTo:
632
                log.debug('IPlanet', obj.oid, 'Downgraded to', downgradeTo)
633
                obj.plType = downgradeTo
634
                Utils.sendMessage(tran, obj, Const.MSG_DOWNGRADED_PLANET_ECO, obj.oid, downgradeTo)
635
        # record changes
636
        obj.changeBio += obj.storBio
637
        obj.changeEn += obj.storEn
638
        obj.changePop += obj.storPop
639
        obj.changeEnv += obj.plEnv
640
641
    @public(Const.AL_ADMIN)
642
    def processPRODPhase(self, tran, obj, data):
643
        if obj.plType == "A":
644
            self.cmd(obj).generateAsteroid(tran, obj)
645
        # max storage
646
        obj.maxPop = obj.plSlots * Rules.popPerSlot + Rules.popBaseStor
647
        obj.maxBio = obj.plSlots * Rules.bioPerSlot + Rules.bioBaseStor
648
        obj.maxEn = obj.plSlots * Rules.enPerSlot + Rules.enBaseStor
649
        # refuel & repair
650
        obj.refuelMax = 0
651
        obj.refuelInc = 0
652
        obj.repairShip = 0.0
653
        obj.upgradeShip = 0.0
654
        # train
655
        obj.trainShipInc = 0
656
        obj.trainShipMax = 0
657
        obj.fleetSpeedBoost = 1.0
658
        #
659
        if obj.storPop <= 0 and not obj.slots and obj.owner == Const.OID_NONE:
660
            # do not process this planet
661
            return
662
        obj.scannerPwr = Rules.scannerMinPwr
663
        obj.prodProd = obj.prodSci = 0
664
        obj.changeBio = - obj.storBio
665
        obj.changeEn = - obj.storEn
666
        obj.changePop = - obj.storPop
667
        obj.changeEnv = - obj.plEnv
668
        obj.changeMorale = - obj.morale
669
        # parent objects
670
        system = tran.db[obj.compOf]
671
        galaxy = tran.db[system.compOf]
672
        # collect strategic resources
673
        owner = tran.db.get(obj.owner, None)
674
        if owner and obj.plStratRes != Const.SR_NONE:
675
            turn = tran.db[Const.OID_UNIVERSE].turn
676
            if turn % Rules.stratResRate == 0:
677
                owner.stratRes[obj.plStratRes] = owner.stratRes.get(obj.plStratRes, 0) + Rules.stratResAmountBig
678
                Utils.sendMessage(tran, obj, Const.MSG_EXTRACTED_STRATRES, obj.oid, obj.plStratRes)
679
        # compute base morale
680
        if owner:
681
            homePlanet = tran.db[owner.planets[0]]
682
            dist = int(math.sqrt((homePlanet.x - obj.x) ** 2 + (homePlanet.y - obj.y) ** 2))
683
            moraleTrgt = -37.5 * dist / owner.govPwrCtrlRange + 107.5
684
            obj.moraleModifiers[0] = max(Rules.minMoraleTrgt, min(moraleTrgt, Rules.maxMorale))
685
            #@log.debug(obj.oid, "Morale target", obj.moraleTrgt, "dist", dist, owner.govPwrCtrlRange)
686
        # auto regulation of min resources
687
        if obj.autoMinStor:
688
            obj.minBio = obj.minEn = 0
689
        # combat?
690
        isCombat = system.combatCounter > 0
691
        obj.unemployedPop = obj.storPop
692
        # ok, reset max pop
693
        obj.maxPop = 0
694
        # process all structures
695
        obj.maxShield = 0
696
        obj.solarmod = 0
697
        #@log.debug("Morale bonus/penalty for planet", obj.oid, moraleBonus)
698
        # reset of "morale modifier by buildings" value
699
        obj.moraleModifiers[1] = 0
700
        self._processStructs(tran, obj)
701
        if obj.revoltLen > 0 or isCombat:
702
            # no services available if distressed
703
            obj.refuelInc = obj.repairShip = obj.upgradeShip = obj.trainShipMax = obj.trainShipInc = 0
704
        # do shield self generation
705
        obj.prevShield = obj.shield #for planet display of shield growth
706
        if obj.maxShield < obj.shield:
707
            obj.shield = obj.maxShield
708
        if obj.maxShield > obj.shield and not isCombat:
709
            regenTemp = max(1, Rules.plShieldRegen* obj.maxShield) #always regen at at least 1
710
            obj.shield = min(obj.shield + regenTemp, obj.maxShield) #don't let it regen over shieldMax
711
        # pass scanner/... to the system
712
        obj.scannerPwr = min(obj.scannerPwr * (2.0 - galaxy.emrLevel), Rules.scannerMaxPwr)
713
        system.scannerPwrs[obj.owner] = max(obj.scannerPwr, system.scannerPwrs.get(obj.owner, 0))
714
715
        self._processPopulation(obj, owner)
716
        self._processProduction(tran, obj, owner)
717
        self._processEnvironmentChange(tran, obj)
718
        # auto regulation of min resources
719
        if obj.autoMinStor:
720
            obj.minBio = min(obj.minBio, obj.maxBio / 2)
721
            obj.minEn = min(obj.minEn, obj.maxEn / 2)
722
        # science
723
        if owner:
724
            moraleBonus = Rules.moraleProdBonus[int(obj.morale / Rules.moraleProdStep)]
725
            obj.effProdSci = max(0, int(obj.prodSci * (owner.sciEff + moraleBonus)))
726
            owner.sciPoints += obj.effProdSci
727
        # planet with no population cannot have an owner
728
        # and planet with no owner cannot have population
729
        if (obj.storPop <= 0 and obj.owner != Const.OID_NONE) or obj.owner == Const.OID_NONE:
730
            self.cmd(obj).changeOwner(tran, obj, Const.OID_NONE, force = 1)
731
            obj.storPop = 0
732
733
    @public(Const.AL_ADMIN)
734
    def processACTIONPhase(self, tran, obj, data):
735
        return
736
737
    @public(Const.AL_ADMIN)
738
    def processFINALPhase(self, tran, obj, data):
739
        if obj.storPop <= 0 and not obj.slots and obj.owner == Const.OID_NONE:
740
            # do not process this planet
741
            return
742
        # reset of "morale modifier by population" value
743
        obj.moraleModifiers[2] = 0
744
        system = tran.db[obj.compOf]
745
        galaxy = tran.db[system.compOf]
746
        if galaxy.timeEnabled:
747
            owner = tran.db.get(obj.owner, None)
748
            # too much population affects morale (if there is more than base population)
749
            if obj.storPop > Rules.moraleBasePop:
750
                obj.moraleModifiers[2] -= Rules.moraleHighPopPenalty * obj.storPop / Rules.moraleBasePop
751
            elif obj.storPop <= Rules.moraleLowPop:
752
                obj.moraleModifiers[2] += Rules.moraleLowPopBonus
753
            else:
754
                # gradually removing LowPop bonus as we approach BasePop - big jumps are awful game
755
                # mechanic
756
                moraleBonusRange = Rules.moraleBasePop - Rules.moraleLowPop
757
                moraleBonus = float(obj.storPop - Rules.moraleLowPop) / moraleBonusRange
758
                obj.moraleModifiers[2] += int(Rules.moraleLowPopBonus * (1 - moraleBonus) )
759
            # there is effect of unemployed population
760
            # if there is none, there is a hit, if there is what's necessary, there is a bonus
761
            # effect between the two is linear
762
            idealUnemployedPop =  obj.plSlots * getattr(owner, "techLevel", 1) * Rules.tlPopReserve
763
            moraleBonusRange = Rules.unemployedMoraleHigh - Rules.unemployedMoraleLow
764
            unemployedRatio = min(1.0, float(obj.unemployedPop) / idealUnemployedPop)
765
            obj.moraleModifiers[3] = Rules.unemployedMoraleLow + int(moraleBonusRange * unemployedRatio)
766
            # count final morale values
767
            obj.moraleModifiers[4] = obj.moraleModifiers[0] +obj.moraleModifiers[1] + obj.moraleModifiers[2] + obj.moraleModifiers[3]
768
            obj.moraleTrgt = obj.moraleModifiers[4]
769
            obj.moraleTrgt = max(0.0, min(obj.moraleTrgt, Rules.maxMorale))
770
            if obj.morale > int(obj.moraleTrgt):
771
                obj.morale -= max(1.0, (obj.morale - obj.moraleTrgt) * Rules.moraleChngPerc)
772
            elif obj.morale < int(obj.moraleTrgt) and system.combatCounter == 0:
773
                obj.morale += max(1.0, (obj.moraleTrgt - obj.morale) * Rules.moraleChngPerc)
774
            #@log.debug('IPlanet', 'Mor Mor trgt/reb thr', obj.morale, obj.moraleTrgt)
775
            # revolt?
776
            if obj.revoltLen > 0:
777
                obj.revoltLen += 1
778
            if obj.morale < Rules.revoltThr and obj.owner != Const.OID_NONE and obj.revoltLen == 0:
779
                chance = (Rules.revoltThr - obj.morale) * Rules.moralePerPointChance
780
                #@log.debug('IPlanet', 'Start revolt? mor, mor trgt, reb thr, chance', obj.morale, obj.moraleTrgt, chance)
781
                if  Utils.rand(0, 101) <= chance:
782
                    # rebelion starts
783
                    #@log.debug('IPlanet', 'Revolt on', obj.oid)
784
                    obj.revoltLen = 1
785
                    Utils.sendMessage(tran, obj, Const.MSG_REVOLT_STARTED, obj.oid, None)
786
            elif obj.revoltLen > 0 and obj.morale > Rules.revoltThr:
787
                chance = (obj.morale - Rules.revoltThr) * Rules.moralePerPointChance
788
                #@log.debug('IPlanet', 'Stop revolt? mor, mor trgt, reb thr, chance', obj.morale, obj.moraleTrgt, chance)
789
                if Utils.rand(0, 101) <= chance:
790
                    # revolt ends
791
                    obj.revoltLen = 0
792
                    Utils.sendMessage(tran, obj, Const.MSG_REVOLT_ENDED, obj.oid, None)
793
            obj.morale = max(0.0, min(Rules.maxMorale, obj.morale))
794
            obj.changeMorale += obj.morale
795
            # when rebelling destroy some resources
796
            if obj.revoltLen > 0:
797
                obj.storBio -= int(obj.storBio * Rules.revoltDestrBio)
798
                obj.storEn -= int(obj.storEn * Rules.revoltDestrEn)
799
            # storage
800
            obj.storBio = min(obj.storBio, obj.maxBio)
801
            obj.storEn = min(obj.storEn, obj.maxEn)
802
        # collect stats
803
        if obj.owner != Const.OID_NONE:
804
            player = tran.db[obj.owner]
805
            player.stats.storPop += obj.storPop
806
            player.stats.prodProd += obj.prodProd
807
            player.stats.effProdProd += obj.effProdProd
808
            player.stats.prodSci += obj.prodSci
809
            player.stats.effProdSci += obj.effProdSci
810
            player.stats.structs += len(obj.slots)
811
            player.stats.slots += obj.plSlots
812
            # morale computation
813
            homePlanet = tran.db[player.planets[0]]
814
            dist = int(math.sqrt((homePlanet.x - obj.x) ** 2 + (homePlanet.y - obj.y) ** 2))
815
            player.tmpPopDistr[dist] = player.tmpPopDistr.get(dist, 0) + obj.storPop
816
817
    def getScanInfos(self, tran, obj, scanPwr, player):
818
        if scanPwr >= Rules.level1InfoScanPwr:
819
            result = IDataHolder()
820
            result._type = Const.T_SCAN
821
            result.scanPwr = scanPwr
822
            result.oid = obj.oid
823
            result.signature = obj.signature
824
            result.type = obj.type
825
            result.orbit = obj.orbit
826
            result.compOf = obj.compOf
827
            result.x = obj.x
828
            result.y = obj.y
829
            result.plType = obj.plType
830
        if scanPwr >= Rules.level2InfoScanPwr:
831
            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 818 is False. Are you sure this can never be the case?
Loading history...
832
            if getattr(obj, "plType", 'X') != 'G':
833
                result.plMin = obj.plMin
834
            result.plBio = obj.plBio
835
            result.plEn = obj.plEn
836
            result.plSlots = obj.plSlots
837
            result.plStratRes = obj.plStratRes
838
            result.plMaxSlots = obj.plMaxSlots
839
        if scanPwr >= Rules.level3InfoScanPwr:
840
            result.name = obj.name
841
            result.storPop = obj.storPop
842
            result.owner = obj.owner
843
        if scanPwr >= Rules.level4InfoScanPwr:
844
            # TODO provide less information
845
            result.hasRefuel = (obj.refuelInc > 0) #simple detect if docks exist for problems dialog
846
            result.slots = obj.slots
847
            result.shield = obj.shield
848
            result.prevShield = -1
849
            result.maxShield = -1
850
        if scanPwr >= Rules.partnerScanPwr:
851
            result.maxShield = obj.maxShield
852
            result.prevShield = obj.prevShield
853
            result.refuelMax = obj.refuelMax
854
            result.refuelInc = obj.refuelInc
855
            result.scannerPwr = obj.scannerPwr
856
            result.trainShipInc = obj.trainShipInc
857
            result.trainShipMax = obj.trainShipMax
858
            result.upgradeShip = obj.upgradeShip
859
            result.repairShip = obj.repairShip
860
            result.fleetSpeedBoost = obj.fleetSpeedBoost
861
        return [result]
862
863
    def loadDOMNode(self, tran, obj, xoff, yoff, orbit, node):
864
        obj.x = xoff
865
        obj.y = yoff
866
        obj.orbit = orbit
867
        for elem in node.childNodes:
868
            if elem.nodeType == Node.ELEMENT_NODE:
869
                name = elem.tagName
870
                if name == 'properties':
871
                    self.loadDOMAttrs(obj, elem)
872
                elif name == 'startingpoint':
873
                    galaxy = tran.db[tran.db[obj.compOf].compOf]
874
                    galaxy.startingPos.append(obj.oid)
875
                    galaxy.numOfStartPos += 1
876
                else:
877
                    raise ige.GameException('Unknown element %s' % name)
878
        return Const.SUCC
879
880
    def update(self, tran, obj):
881
        # clean up negative build queues and fix missing demolishStruct keys
882
        loopAgain = True
883
884
        while loopAgain:
885
            deletedKey = False
886
            for key in range(0,len(obj.prodQueue)):
887
                item = obj.prodQueue[key]
888
                if not hasattr(item, "demolishStruct"):
889
                    item.demolishStruct = Const.OID_NONE
890
                if item.quantity < 0:
891
                    log.warning("Deleting negative item queue on", obj.oid,"for player",obj.owner)
892
                    if item.isShip:
893
                        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...
894
                    else:
895
                        tech = Rules.techs[item.techID]
896
                    player = tran.db[obj.owner]
897
                    for sr in tech.buildSRes:
898
                        player.stratRes[sr] = player.stratRes.get(sr, 0) + item.quantity #quantity negative, so subtracting strat resources
899
                    # del the bad item. Since this changes indicies, start the check over again on remaining items
900
                    deletedKey = True
901
                    del obj.prodQueue[key]
902
                    break
903
            # no more bad entries found; break the while loop
904
            if not deletedKey:
905
                loopAgain = False
906
907
        # change owner to Const.OID_NONE when owner is invalid
908
        if obj.owner != Const.OID_NONE:
909
            player = tran.db.get(obj.owner, None)
910
            if not player or player.type not in Const.PLAYER_TYPES or obj.oid not in player.planets:
911
                # TODO this can be a probem - this planet cannot be attacked!
912
                log.warning("Changing owner to Const.OID_NONE - invalid owner", obj)
913
                self.cmd(obj).changeOwner(tran, obj, Const.OID_NONE, force = 1)
914
                # kill all population
915
                obj.storPop = 0
916
                return
917
        # check compOf
918
        if not tran.db.has_key(obj.compOf) or tran.db[obj.compOf].type != Const.T_SYSTEM:
919
            log.debug("CONSISTENCY invalid compOf for planet", obj.oid)
920
        # fix signature
921
        obj.signature = 75
922
        if not hasattr(obj, 'moraleModifiers'):
923
            obj.moraleModifiers = [ 0.0 , 0.0 , 0.0 , 0.0, 0.0 ]
924
925
    update.public = 0
926
927
    @public(Const.AL_FULL)
928
    def changePlanetsGlobalQueue(self, tran, obj, newQueue):
929
        player = tran.db[obj.owner]
930
        if newQueue < 0 or newQueue >= len(player.prodQueues):
931
            raise ige.GameException("Invalid queue")
932
        obj.globalQueue = newQueue
933
        return obj.globalQueue
934
935
    def popGlobalQueue(self, tran, obj):
936
        player = tran.db[obj.owner]
937
        queue = obj.globalQueue
938
        task = None
939
        if len(player.prodQueues[queue]):
940
            task = copy.copy(player.prodQueues[queue][0])
941
            if task.quantity > 1:
942
                player.prodQueues[queue][0].quantity -= 1
943
            else:
944
                if task.reportFin:
945
                    Utils.sendMessage(tran, obj, Const.MSG_QUEUE_TASK_ALLOTED, Const.OID_NONE, (queue, task.techID))
946
                del player.prodQueues[queue][0]
947
            # add other demanded values, report finalization was used to report allot (to prevent reporting every unit)
948
            task.reportFin = 0
949
            task.quantity = 1
950
            task.isShip = task.techID < 1000
951
            task.targetID = obj.oid
952
            task.currProd = 0
953
            task.demolishStruct = Const.OID_NONE
954
        return task
955
956
    popGlobalQueue.public = 0
957
958
    def deleteDesign(self, tran, obj, designID, keepWIP = 0):
959
        # TODO: handle stategic resources
960
        for task in obj.prodQueue[:]:
961
            if task.isShip and task.techID == designID:
962
                if task.currProd > 0 and keepWIP:
963
                    self.cmd(obj).changeConstruction(tran, obj, obj.procQueue.index(task), 1)
964
                else:
965
                    self.cmd(obj).abortConstruction(tran, obj, obj.prodQueue.index(task))
966
967
    deleteDesign.public = 0
968
969
    def changeShipDesign(self, tran, obj, oldDesignID, newDesignID):
970
        # TODO: handle strategic resources
971
        for task in obj.prodQueue[:]:
972
            if task.isShip and task.techID == oldDesignID:
973
                task.techID = newDesignID
974
                task.currProd = int(task.currProd / Rules.shipUpgradeMod)
975
976
    changeShipDesign.public = 0
977
978
    ##
979
    ## Asteroids
980
    ##
981
982
    def generateAsteroid(self, tran, obj):
983
        return
984
        assert obj.plType == "A"
985
        #
986
        modifier = pow(
987
            max(Rules.asteroidMinPlMinAbund / 100.0, obj.plMin / 100.0),
988
            Rules.asteroidModPwr,
989
        )
990
        # get probability
991
        prob = Rules.asteroidGenerPerc * modifier
992
        #@log.debug("Asteroids ?", prob, modifier, int(Rules.asteroidMinHP * modifier), int(Rules.asteroidMaxHP * modifier))
993
        if prob < random.random():
994
            # bad luck
995
            return
996
        # new asteroid - gener hit points and speed
997
        hp = random.randrange(
998
            int(Rules.asteroidMinHP * modifier),
999
            int(Rules.asteroidMaxHP * modifier)
1000
        )
1001
        speed = Rules.asteroidMinSpeed + random.random() * \
1002
            (Rules.asteroidMaxSpeed - Rules.asteroidMinSpeed)
1003
        # position
1004
        system = tran.db[obj.compOf]
1005
        # select target
1006
        if Rules.asteroidTargetInSystem < random.random():
1007
            # TODO: target nearby system
1008
            objIDs = []
1009
            # pick one target (except this system)
1010
            while True:
1011
                systemID = random.choice(objIDs)
1012
                tmpSystem = tran.db[systemID]
1013
                if tmpSystem.type == Const.T_SYSTEM and systemID != system.oid:
1014
                    break
1015
            # select planet
1016
            targetID = random.choice(tmpSystem.planets)
1017
        else:
1018
            # select planet in this system
1019
            while True:
1020
                targetID = random.choice(system.planets)
1021
                if targetID != obj.oid:
1022
                    # don't target yourself
1023
                    break
1024
        # create asteroid
1025
        asteroid = self.new(Const.T_ASTEROID)
1026
        tran.db.create(asteroid)
1027
        self.cmd(asteroid).create(tran, asteroid, system.x, system.y, targetID, speed, hp)
1028
1029
    ##
1030
    ## Combat related functions
1031
    ##
1032
1033
    def getPreCombatData(self, tran, obj):
1034
        # scan buildings and fire their weapons
1035
        shots = {0: [], 1: [], 2: [], 3: []}
1036
        if obj.owner == Const.OID_NONE:
1037
            return shots, [0, 0, 0, 8], False
1038
        player = tran.db[obj.owner]
1039
        system = tran.db[obj.compOf]
1040
        desCount = {}
1041
        firing = False
1042
        systemAtt = 0;
1043
        systemDef = 0;
1044
        for struct in obj.slots:
1045
            structTechID = struct[Const.STRUCT_IDX_TECHID]
1046
            opStatus = struct[Const.STRUCT_IDX_OPSTATUS] / 100.0
1047
            tech = Rules.techs[structTechID]
1048
            desCount[structTechID] = desCount.get(structTechID, 0) + 1
1049
            wpnCount = {}
1050
            if not tech.structWeapons:
1051
                continue
1052
            firing = True
1053
            for cClass in range(0, 4):
1054
                weaponID = player.planetWeapons[cClass]
1055
                if weaponID is None:
1056
                    continue
1057
                weapon = Rules.techs[weaponID]
1058
                maxWeaponCount = int(tech.structWeapons[cClass] * opStatus)
1059
                for weaponIdx in range(0, maxWeaponCount):
1060
                    #@log.debug(obj.oid, "FIRING PLANET WEAPON", weapon.name)
1061
                    wpnCount[weaponID] = wpnCount.get(weaponID, 0) + 1
1062
                    #
1063
                    weaponEff = Rules.techImprEff[player.techs.get(weaponID, Rules.techBaseImprovement)]
1064
                    # base attack
1065
                    attack = tech.combatAtt + int(weapon.weaponAtt * weaponEff)
1066
                    # because ALL counters starts at 1, subtract 3
1067
                    count = system.combatCounter + desCount[structTechID] + wpnCount[weaponID] - 2
1068
                    # add to attacks
1069
                    #@log.debug('IPlanet', obj.oid, structTechID, "Count", count, 'Shots', weapon.name, ShipUtils.getRounds(weapon.weaponROF, count))
1070
                    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...
1071
                        shots[weapon.weaponClass].append((attack, weaponID))
1072
        # hit limit
1073
        obj.maxHits = len(obj.slots)
1074
        obj.hitCounter = 0
1075
        obj.lastHitClass = 3
1076
        obj.hitMod = 1.0
1077
        log.debug(obj.oid, "Combat settings", obj.maxHits)
1078
        # +1 means population only hit
1079
        return shots, [0, 0, 0, 8], firing
1080
1081
    getPreCombatData.public = 0
1082
1083
    def applyShot(self, tran, obj, defense, attack, weaponID, cClass, count):
1084
        #@log.debug('IPlanet', 'Apply shot', weaponID, attack, cClass, count)
1085
        # compute chance to hit
1086
        weapon = Rules.techs[weaponID]
1087
        #system defense bonus is dropped for planets...structures can't move; just calculate defense off structure defense
1088
        defense = Rules.combatStructDefense
1089
        destroyed = 0
1090
        dmg = 0
1091
        # limit number of shots
1092
        if weapon.weaponClass < obj.lastHitClass:
1093
            #@log.debug(obj.oid, "Different class", obj.lastHitClass, weapon.weaponClass, obj.maxHits)
1094
            obj.maxHits = int(Rules.combatHitXferMod * obj.maxHits * (obj.lastHitClass - weapon.weaponClass))
1095
            obj.hitCounter = int(Rules.combatHitXferMod * obj.hitCounter * (obj.lastHitClass - weapon.weaponClass))
1096
            obj.lastHitClass = weapon.weaponClass
1097
        if weapon.weaponROF > 1:
1098
            #@log.debug(obj.oid, "Increasing counter PL", 1.0 / weapon.weaponROF)
1099
            obj.hitCounter += 1.0 / weapon.weaponROF
1100
        else:
1101
            #@log.debug(obj.oid, "Increasing counter PL", 1)
1102
            obj.hitCounter += 1
1103
        if obj.hitCounter > obj.maxHits:
1104
            obj.hitCounter = 0
1105
            obj.hitMod *= Rules.combatStructureHitMod
1106
            #@log.debug(obj.oid, "Increasing hit penalty", obj.hitMod, obj.maxHits)
1107
        attackChance = obj.hitMod * attack / (attack + defense)
1108
        #@log.debug(obj.oid, "Chance to attack", attackChance, obj.hitMod, obj.hitCounter, obj.maxHits,
1109
        #@    "without penalty:", float(attack) / (attack + defense))
1110
        #@log.debug('IPlanet', obj.oid, 'HIT?', attack + defense + 1, defense)
1111
        absorb = 0 #for when it doesn't hit
1112
        if random.random() <= attackChance:
1113
            # hit
1114
            player = tran.db[obj.owner]
1115
            weaponEff = Rules.techImprEff[player.techs.get(weaponID, Rules.techBaseImprovement)]
1116
            dmg = ShipUtils.computeDamage(weapon.weaponClass, 3, weapon.weaponDmgMin, weapon.weaponDmgMax, weaponEff)
1117
            #@log.debug(obj.oid, 'HIT! att=%d vs def=%d, dmg=%d '% (attack, defense, dmg))
1118
            #shield strike
1119
            if obj.shield > 0:
1120
                absorb = min(dmg,obj.shield)
1121
                obj.shield -= absorb
1122
                dmg -= absorb
1123
            if dmg == 0:
1124
                return 0+absorb, 0, 3
1125
            # select slot
1126
            if count == 7 or not obj.slots:
1127
                #@log.debug('IPlanet', 'Population hit')
1128
                # population hit
1129
                if obj.storPop == 0:
1130
                    dmg = 0
1131
                else:
1132
                    # free slot hit -> dmg population
1133
                    # OLD dmgPop = int(Rules.popPerSlot * float(dmg) / Rules.popSlotHP * Rules.popKillMod)
1134
                    dmgPop = int(dmg * Rules.popSlotKillMod)
1135
                    obj.storPop = max(obj.storPop - dmgPop, 0)
1136
                    obj.changePop -= dmgPop
1137
                    if obj.storPop > 0:
1138
                        obj.morale -= Rules.moraleModPlHit * float(dmgPop) / float(obj.storPop)
1139
                    #@log.debug('IPlanet', obj.oid, 'Morale penalty', dmg, maxHP, Rules.moraleModPlHit * float(dmg) / float(maxHP))
1140
            elif count < 0:
1141
                # TODO can be count negative?
1142
                log.warning('IPlanet', 'applyShot: count is negative')
1143
            else:
1144
                if count == 6:
1145
                    # random structure hit
1146
                    #@log.debug('IPlanet', 'Random structure hit')
1147
                    struct = obj.slots[Utils.rand(0, len(obj.slots))]
1148
                else:
1149
                    # most damaged structure hit
1150
                    #@log.debug('IPlanet', 'Most damaged structure hit')
1151
                    struct = obj.slots[-1]
1152
                    for tmpStruct in obj.slots:
1153
                        if tmpStruct[Const.STRUCT_IDX_HP] <= struct[Const.STRUCT_IDX_HP]:
1154
                            struct = tmpStruct
1155
                # compute sum hp of all buildings
1156
                sumHP = 0
1157
                for tmpStruct in obj.slots:
1158
                    sumHP += tmpStruct[Const.STRUCT_IDX_HP]
1159
                # damage building
1160
                struct[Const.STRUCT_IDX_HP] -= dmg
1161
                # "damage" population
1162
                tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
1163
                # compute struct effectivity
1164
                techEff = Utils.getTechEff(tran, struct[Const.STRUCT_IDX_TECHID], obj.owner)
1165
                maxHP = int(tech.maxHP * techEff)
1166
                dmgPop = int(tech.operWorkers * float(dmg) / maxHP * Rules.popKillMod)
1167
                obj.storPop = max(obj.storPop - dmgPop, 0)
1168
                obj.changePop -= dmgPop
1169
                # destroy building
1170
                if struct[Const.STRUCT_IDX_HP] <= 0:
1171
                    destroyed = 1
1172
                    dmg += struct[Const.STRUCT_IDX_HP]
1173
                    obj.slots.remove(struct)
1174
                # compute morale penalty
1175
                if dmg:
1176
                    obj.morale -= Rules.moraleModPlHit * float(dmg) / float(sumHP)
1177
                    #@log.debug('IPlanet', obj.oid, 'Morale penalty', dmg, sumHP, Rules.moraleModPlHit * float(dmg) / float(sumHP))
1178
        #@log.debug('IPlanet', 'Shot applied', dmg, destroyed)
1179
        # when destroyed, only class 3 (structure) i valid
1180
        return dmg+absorb, destroyed, 3
1181
1182
    applyShot.public = 0
1183
1184
    def distributeExp(self, tran, obj):
1185
        # TODO - will buildings have exp? Answ: NO
1186
        if hasattr(obj, "maxHits"):
1187
            del obj.maxHits
1188
            del obj.hitCounter
1189
            del obj.lastHitClass
1190
            del obj.hitMod
1191
1192
    distributeExp.public = 0
1193
1194
    def surrenderTo(self, tran, obj, newOwnerID):
1195
        # morale is lost when this is called
1196
        obj.morale -= Rules.moraleLostWhenSurrender
1197
        if obj.morale >= Rules.revoltThr:
1198
            #@log.debug('IPlanet', 'Surrender - revolt thr not reached', obj.morale)
1199
            return 0
1200
        chance = (Rules.revoltThr - obj.morale) * Rules.moralePerPointChance
1201
        #@log.debug('IPlanet', 'Surrender? mor, mor trgt, reb thr, chance', obj.morale, obj.moraleTrgt, chance)
1202
        if Utils.rand(0, 101) > chance:
1203
            # do not surrender!
1204
            #@log.debug('IPlanet', 'Surrender - pure luck', obj.morale, obj.revoltLen)
1205
            return 0
1206
        # we've lost the battle - we have a new owner
1207
        #@log.debug('IPlanet', 'Surrender - surrending to', newOwnerID)
1208
        newOwner = tran.db[newOwnerID]
1209
        if newOwner.type == Const.T_PIRPLAYER or newOwner.type == Const.T_AIPIRPLAYER:
1210
            # special handling for pirates
1211
            currentTurn = tran.db[Const.OID_UNIVERSE].turn
1212
            # 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)
1213
            if (currentTurn - obj.lastPirCapture) > 8 and (currentTurn - obj.ownerSince) > 2:
1214
                # gain/lose fame
1215
                self.cmd(newOwner).capturePlanet(tran, newOwner, obj)
1216
                # steal ship techs
1217
                self.cmd(newOwner).stealTechs(tran, newOwner, obj.owner, obj.oid)
1218
            else:
1219
                log.debug(obj.oid, "Pirate captured planet too soon after previous capture or colonization to gain bonuses", obj.oid)
1220
            obj.storPop = 0
1221
            obj.lastPirCapture = currentTurn
1222
            self.cmd(obj).changeOwner(tran, obj, Const.OID_NONE, force = 1)
1223
        else:
1224
            # change owner
1225
            self.cmd(obj).changeOwner(tran, obj, newOwnerID, force = 1)
1226
        # blow up all military buildings
1227
        for struct in obj.slots[:]:
1228
            tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
1229
            if tech.isMilitary:
1230
                obj.slots.remove(struct)
1231
        return 1
1232
1233
    surrenderTo.public = 0
1234