Completed
Pull Request — master (#192)
by Marek
01:39
created

ige.ospace.IPlanet.IPlanet.moveConstrItem()   A

Complexity

Conditions 4

Size

Total Lines 10
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 10
nop 5
dl 0
loc 10
rs 9.9
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 = techOper - techProd * prodMod
297
        if slope >= 0:
298
            # structure is self-sufficient in this aspect
299
            return 1.0
300
        else:
301
            return min(stor / - slope, 1.0)
302
303
    def _getStructStatus(self, obj, struct, tech, maxHP):
304
        # find most limitating condition
305
        if not struct[Const.STRUCT_IDX_STATUS] & Const.STRUCT_STATUS_ON:
306
            return 0.0
307
        try:
308
            opStatusHP = min(1.0, float(struct[Const.STRUCT_IDX_HP]) / maxHP)
309
        except:
310
            opStatusHP = 0.0
311
            log.warning('Invalid max HP of structure', Const.STRUCT_IDX_TECHID)
312
        opStatusBio = self._getOpStatus(obj, tech.prodBioMod, tech.operBio, tech.prodBio, obj.storBio)
313
        opStatusEn = self._getOpStatus(obj, tech.prodEnMod, tech.operEn, tech.prodEn, obj.storEn)
314
        opStatusPop = min(1.0, float(obj.unemployedPop) / tech.operWorkers)
315
        opStatus = min(opStatusHP, opStatusBio, opStatusEn, opStatusPop)
316
        if opStatus < 1.0:
317
            if opStatusBio == opStatus:
318
                struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_NOBIO
319
            if opStatusEn == opStatus:
320
                struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_NOEN
321
            if opStatusPop == opStatus:
322
                struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_NOPOP
323
        struct[Const.STRUCT_IDX_OPSTATUS] = int(100 * opStatus)
324
        return opStatusHP, min(opStatusBio, opStatusEn, opStatusPop)
325
326
    def _updateStructHP(self, obj, struct, opStatuses, maxHP):
327
        # auto repair/damage
328
        # also damage structures on not owned planets
329
        opStatusHP, opStatusOther = opStatuses
330
        properHP = opStatusOther * maxHP
331
        repairDiff = min(properHP - struct[Const.STRUCT_IDX_HP], Rules.repairRunningRatio * maxHP)
332
        decayDiff = min(struct[Const.STRUCT_IDX_HP] - properHP, Rules.decayRatio * maxHP)
333
        if struct[Const.STRUCT_IDX_HP] < properHP:
334
            struct[Const.STRUCT_IDX_HP] += repairDiff
335
            struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_REPAIRING
336
        elif struct[Const.STRUCT_IDX_HP] > properHP:
337
            # flag only for non functional structure
338
            struct[Const.STRUCT_IDX_STATUS] |= Const.STRUCT_STATUS_DETER
339
            # damage it a bit
340
            struct[Const.STRUCT_IDX_HP] -= decayDiff
341
            if obj.storPop > 0:
342
                # do not fall below 1 HP for populated planets so it won't destroy buildings
343
                struct[Const.STRUCT_IDX_HP] = max(struct[Const.STRUCT_IDX_HP], 1)
344
        if struct[Const.STRUCT_IDX_HP] <= 0:
345
            obj.slots.remove(struct)
346
347
    def _processStructs(self, tran, obj):
348
        for struct in obj.slots[:]:
349
            # skip structure if it was built this turn
350
            if struct[Const.STRUCT_IDX_STATUS] & Const.STRUCT_STATUS_NEW:
351
                continue
352
            tech = Rules.techs[struct[Const.STRUCT_IDX_TECHID]]
353
            # compute struct effectivity
354
            techEff = Utils.getTechEff(tran, struct[Const.STRUCT_IDX_TECHID], obj.owner)
355
            # morale does not affect hit points of structures
356
            maxHP = int(tech.maxHP * techEff)
357
            # auto regulation of min resources
358
            if obj.autoMinStor:
359
                obj.minBio += tech.operBio * Rules.autoMinStorTurns
360
                obj.minEn += tech.operEn * Rules.autoMinStorTurns
361
            # produce/consume resources
362
            opStatuses = self._getStructStatus(obj, struct, tech, maxHP)
363
            self._updateStructHP(obj, struct, opStatuses, maxHP)
364
            opStatus = min(opStatuses)
365
            # solarmod effects ENV change and terraforming only if benificial
366
            if tech.solarMod * opStatus > 0:
367
                obj.solarmod = max(obj.solarmod, tech.solarMod * techEff * opStatus)
368
            elif tech.solarMod * opStatus < 0:
369
                obj.solarmod = min(obj.solarmod, tech.solarMod * techEff * opStatus)
370
            # bio
371
            prodMod = self._getStructProdMod(obj, tech.prodBioMod)
372
            obj.storBio += int(tech.prodBio * prodMod * techEff * opStatus) - int(tech.operBio * opStatus)
373
            # en
374
            prodMod = self._getStructProdMod(obj, tech.prodEnMod)
375
            obj.storEn += int(tech.prodEn * prodMod * techEff * opStatus) - int(tech.operEn * opStatus)
376
377
            obj.unemployedPop -= min(obj.unemployedPop, int(tech.operWorkers * opStatus))
378
            obj.storPop += int(tech.prodPop * techEff * opStatus)
379
            obj.scannerPwr = max(int(tech.scannerPwr * techEff * opStatus), obj.scannerPwr)
380
            obj.scannerPwr = min(obj.scannerPwr, Rules.scannerMaxPwr)
381
            # rebellion and combat has common penalty
382
            prodMod = self._getStructProdMod(obj, tech.prodProdMod)
383
            obj.prodProd += int(tech.prodProd * prodMod * techEff * opStatus)
384
            # science
385
            prodMod = self._getStructProdMod(obj, tech.prodSciMod)
386
            obj.prodSci += int(tech.prodSci * prodMod * techEff * opStatus)
387
            # refuelling & repairing
388
            obj.refuelMax = max(obj.refuelMax, int(tech.refuelMax * techEff * opStatus))
389
            # refuelling
390
            obj.refuelInc = max(obj.refuelInc, int(tech.refuelInc * techEff * opStatus))
391
            # repair
392
            obj.repairShip += tech.repairShip * techEff * opStatus
393
            obj.upgradeShip += tech.upgradeShip * techEff * opStatus
394
            # train
395
            obj.trainShipMax = max(obj.trainShipMax, tech.trainShipMax)
396
            obj.trainShipInc = max(obj.trainShipInc, tech.trainShipInc * techEff * opStatus)
397
            # shielding
398
            obj.maxShield = max(tech.planetShield * techEff * opStatus, obj.maxShield)
399
            # stargates
400
            obj.fleetSpeedBoost = max(obj.fleetSpeedBoost, tech.fleetSpeedBoost * techEff * opStatus)
401
            # storage
402
            obj.maxBio += int(tech.storBio * techEff)
403
            obj.maxEn += int(tech.storEn * techEff)
404
            # each structure accomodate it's workers
405
            obj.maxPop += tech.operWorkers
406
            obj.maxPop += int(tech.storPop * techEff)
407
            obj.plEnv += int(tech.prodEnv * techEff * opStatus)
408
            # morale modifier of the building
409
            obj.moraleModifiers[1] += tech.moraleTrgt * techEff * opStatus
410
411
    def _processPopulation(self, obj, owner):
412
        if not obj.storPop:
413
            return
414
        # population reserve
415
        obj.maxPop += obj.plSlots * getattr(owner, "techLevel", 1) * Rules.tlPopReserve
416
        # max pop
417
        maxPop = obj.maxPop
418
        if obj.popEatBio: maxPop = min(maxPop,  1000.0 * obj.storBio / obj.popEatBio)
419
        if obj.popEatEn: maxPop = min(maxPop, 1000.0 * obj.storEn / obj.popEatEn)
420
        maxPop = int(maxPop)
421
        # eat
422
        pop = obj.storPop / 1000.0
423
        wantBio = int(math.ceil(pop * obj.popEatBio))
424
        wantEn = int(math.ceil(pop * obj.popEatEn))
425
        # auto regulation of min resources
426
        if obj.autoMinStor:
427
            obj.minBio += wantBio * Rules.autoMinStorTurns
428
            obj.minEn += wantEn * Rules.autoMinStorTurns
429
        # consume resources
430
        obj.storBio -= min(obj.storBio, wantBio)
431
        obj.storEn -= min(obj.storEn, wantEn)
432
        # modify pop
433
        if obj.storPop > maxPop:
434
            # die
435
            obj.storPop -= max(int((obj.storPop - maxPop) * Rules.popDieRate), Rules.popMinDieRate)
436
            #if obj.storPop < maxPop: obj.storPop = maxPop
437
            # do not generate this message when construction has been destroyed
438
            # and do not lower morale too
439
            if obj.storPop < obj.maxPop:
440
                obj.morale = max(obj.morale - Rules.moraleLostNoFood,0)
441
        elif obj.storPop < maxPop:
442
            # born
443
            obj.storPop += max(min(int(obj.storPop * Rules.popGrowthRate), maxPop - obj.storPop), Rules.popMinGrowthRate)
444
445
    def _buildShip(self, tran, obj, item, owner):
446
        system = tran.db[obj.compOf]
447
        # find commander's fleet
448
        fleet = None
449
        # check if current system has any redirection
450
        hasRedirection = obj.compOf in owner.shipRedirections
451
        for fleetID in system.fleets:
452
            tmpFleet = tran.db[fleetID]
453
            if tmpFleet.owner == obj.owner and Utils.isIdleFleet(tmpFleet):
454
                fleet = tmpFleet
455
                break
456
        if not fleet or hasRedirection:
457
            fleet = self.new(Const.T_FLEET)
458
            tran.db.create(fleet)
459
            self.cmd(fleet).create(tran, fleet, system, obj.owner)
460
            self.cmd(fleet).addAction(tran, fleet, 0, Const.FLACTION_REDIRECT, Const.OID_NONE, None)
461
        # add ships to the fleet
462
        self.cmd(fleet).addNewShip(tran, fleet, item.techID)
463
        if item.reportFin and item.quantity == 1:
464
            Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_SHIP, obj.oid, item.techID)
465
466
    def _buildStructure(self, tran, obj, item, tech, target):
467
        # if there is struct to demolish, find it and delete it
468
        if item.demolishStruct != Const.OID_NONE:
469
            structToDemolish = None
470
            for struct in target.slots:
471
                if struct[Const.STRUCT_IDX_TECHID] == item.demolishStruct:
472
                    target.slots.remove(struct)
473
                    break
474
        if len(target.slots) < target.plSlots:
475
            target.slots.append(Utils.newStructure(tran, item.techID, obj.owner))
476
            try:
477
                tech.finishConstrHandler(tran, obj, target, tech)
478
            except Exception:
479
                log.warning("Cannot execute finish constr handler")
480
            if item.reportFin and item.quantity == 1:
481
                Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_STRUCTURE, target.oid, item.techID)
482
        else:
483
            # no free slot!
484
            Utils.sendMessage(tran, obj, Const.MSG_CANNOTBUILD_NOSLOT, target.oid, None)
485
486
    def _processProduction(self, tran, obj, owner):
487
        # produce items in construction queue
488
        if owner:
489
            moraleBonus = Rules.moraleProdBonus[int(obj.morale / Rules.moraleProdStep)]
490
            prod  = obj.effProdProd = max(0, int(obj.prodProd * (owner.prodEff + moraleBonus)))
491
            if (obj.morale > 15 and prod == 0 and obj.prodProd > 0 and owner.prodEff > 0): #added for super-low moral bonus issues
492
                prod  = obj.effProdProd = 1
493
        else:
494
            prod = obj.prodProd
495
        explicitIdleProd = 0.0
496
        # empty queue should be filled by global queue
497
        if len(obj.prodQueue) == 0 and prod:
498
            task = self.cmd(obj).popGlobalQueue(tran, obj)
499
            if task:
500
                obj.prodQueue.append(task)
501
        index = 0
502
        while prod > 0 and index < len(obj.prodQueue):
503
            item = obj.prodQueue[index]
504
            # check if owner has this tech
505
            if not item.isShip and item.techID not in owner.techs:
506
                # bad tech
507
                del obj.prodQueue[index]
508
                # TODO send message
509
            # set target
510
            target = tran.db[item.targetID]
511
            # set tech and build conditions
512
            if item.isShip:
513
                tech = tran.db[obj.owner].shipDesigns[item.techID]
514
                mod = Rules.buildOnSamePlanetMod
515
            else:
516
                tech = Rules.techs[item.techID]
517
                # check validity of the project
518
                if not tech.validateConstrHandler(tran, obj, target, tech):
519
                    index += 1
520
                    # message to player
521
                    Utils.sendMessage(tran, obj, Const.MSG_INVALID_TASK, obj.oid, item.techID)
522
                    continue
523
                # building on other planet is more expensive
524
                if item.targetID == obj.oid:
525
                    mod = Rules.buildOnSamePlanetMod
526
                else:
527
                    mod = Rules.buildOnAnotherPlanetMod
528
            # compute needs (do not consume resources under minimal storage)
529
            wantProd = min(int(tech.buildProd * mod / tech.buildTurns - item.currProd), prod)
530
            # production
531
            item.changePerc = wantProd * 10000 / (tech.buildProd * mod)
532
            # consume / produce
533
            if item.techID == Rules.Tech.IDLETASK and item.isShip == 0:
534
                explicitIdleProd += wantProd
535
            prod -= wantProd
536
            item.currProd += wantProd
537
            # check, if production is complete
538
            if item.currProd >= tech.buildProd * mod:
539
                # item is complete
540
                if item.isShip:
541
                    self._buildShip(tran, obj, item, owner)
542
                elif tech.isStructure:
543
                    self._buildStructure(tran, obj, item, tech, target)
544
                elif tech.isProject:
545
                    tech.finishConstrHandler(tran, obj, target, tech)
546
                    if item.reportFin and item.quantity == 1:
547
                        Utils.sendMessage(tran, obj, Const.MSG_COMPLETED_PROJECT, target.oid, item.techID)
548
                else:
549
                    raise ige.GameException('Unsupported type of technology %d ' % item.techID)
550
                # remove item from prod queue
551
                item.quantity -= 1
552
                if item.quantity == 0:
553
                    # remove item from the queue
554
                    del obj.prodQueue[index]
555
                    # was it last item in the queue? pop the global one!
556
                    if index == len(obj.prodQueue):
557
                        task = self.cmd(obj).popGlobalQueue(tran, obj)
558
                        if task:
559
                            obj.prodQueue.append(task)
560
561
                else:
562
                    # try to produce another item
563
                    item.currProd = 0
564
            else:
565
                # item is not complete stop production
566
                index += 1
567
                break
568
        # decay items not currently produced
569
        while index < len(obj.prodQueue):
570
            item = obj.prodQueue[index]
571
            item.currProd = max(0, int(item.currProd - max(item.currProd * Rules.decayRatio, 1)))
572
            index += 1
573
        # use excess raw CP to increase production elsewhere
574
        prod += explicitIdleProd
575
        if prod > 0.0:
576
            owner.prodIncreasePool += prod
577
578
    def _processEnvironmentChange(self, tran, obj, owner):
579
        downgradeTo = Rules.planetSpec[obj.plType].downgradeTo
580
        solarminus = min(0, obj.solarmod)
581
        solarplus = max(0, obj.solarmod)
582
        if downgradeTo is not None:
583
            if (Rules.planetSpec[downgradeTo].upgradeEnReqs[0] > obj.plEn + solarplus) or (Rules.planetSpec[downgradeTo].upgradeEnReqs[1] < obj.plEn + solarminus):
584
                # auto damage on plEn outside downgrade's upgrade range
585
                obj.plEnv -= Rules.envAutoMod
586
        if obj.plBio > Rules.planetSpec[obj.plType].maxBio:
587
            # auto damage on plBio > maxBio of class
588
            dEnv = int((obj.plBio - Rules.planetSpec[obj.plType].maxBio) * Rules.envAutoMod)
589
            if obj.plEnv > 0:
590
                obj.plEnv -= min(obj.plEnv, dEnv)
591
            else:
592
                obj.plEnv -= dEnv
593
            # small chance of self-upgrading
594
            spec = Rules.planetSpec[obj.plType]
595
            race = owner.race if owner else "H"
596
            chance = int((obj.plBio - spec.maxBio) * Rules.envSelfUpgradeChance[race])
597
            if Utils.rand(0, 10001) < chance and spec.upgradeTo and \
598
                    obj.plEn + solarplus >= spec.upgradeEnReqs[0] and \
599
                    obj.plEn + solarminus <= spec.upgradeEnReqs[1]:
600
                log.debug('IPlanet', obj.oid, 'Upgraded to', spec.upgradeTo)
601
                obj.plType = spec.upgradeTo
602
                Utils.sendMessage(tran, obj, Const.MSG_UPGRADED_PLANET_ECO, obj.oid, spec.upgradeTo)
603
        while obj.plEnv >= Rules.envInterval:
604
            #@log.debug('IPlanet', obj.oid, 'Env improved')
605
            obj.plEnv -= Rules.envInterval
606
            obj.changeEnv += Rules.envInterval
607
            if obj.plBio < 200: obj.plBio += 1
608
        while obj.plEnv < 0:
609
            if obj.plBio > 0:
610
                obj.plBio -= 1
611
                obj.plEnv += Rules.envInterval
612
                obj.changeEnv -= Rules.envInterval
613
            else:
614
                obj.changeEnv += obj.plEnv
615
                obj.plEnv = 0
616
        # downgrade planet if necessary
617
        if obj.plBio < Rules.planetSpec[obj.plType].minBio:
618
            downgradeTo = Rules.planetSpec[obj.plType].downgradeTo
619
            if downgradeTo:
620
                log.debug('IPlanet', obj.oid, 'Downgraded to', downgradeTo)
621
                obj.plType = downgradeTo
622
                Utils.sendMessage(tran, obj, Const.MSG_DOWNGRADED_PLANET_ECO, obj.oid, downgradeTo)
623
        # record changes
624
        obj.changeBio += obj.storBio
625
        obj.changeEn += obj.storEn
626
        obj.changePop += obj.storPop
627
        obj.changeEnv += obj.plEnv
628
629
    @public(Const.AL_ADMIN)
630
    def processPRODPhase(self, tran, obj, data):
631
        if obj.plType == "A":
632
            self.cmd(obj).generateAsteroid(tran, obj)
633
        # max storage
634
        obj.maxPop = obj.plSlots * Rules.popPerSlot + Rules.popBaseStor
635
        obj.maxBio = obj.plSlots * Rules.bioPerSlot + Rules.bioBaseStor
636
        obj.maxEn = obj.plSlots * Rules.enPerSlot + Rules.enBaseStor
637
        # refuel & repair
638
        obj.refuelMax = 0
639
        obj.refuelInc = 0
640
        obj.repairShip = 0.0
641
        obj.upgradeShip = 0.0
642
        # train
643
        obj.trainShipInc = 0
644
        obj.trainShipMax = 0
645
        obj.fleetSpeedBoost = 1.0
646
        #
647
        if obj.storPop <= 0 and not obj.slots and obj.owner == Const.OID_NONE:
648
            # do not process this planet
649
            return
650
        obj.scannerPwr = Rules.scannerMinPwr
651
        obj.prodProd = 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