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

ige.ospace.IPlanet.IPlanet._getStructStatus()   B

Complexity

Conditions 7

Size

Total Lines 22
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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