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

ige.ospace.IPlanet.IPlanet._buildShip()   C

Complexity

Conditions 8

Size

Total Lines 20
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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