Passed
Pull Request — master (#223)
by Marek
02:00
created

ige.ospace.ShipUtils._checkValidityWhole()   B

Complexity

Conditions 6

Size

Total Lines 11
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 11
nop 4
dl 0
loc 11
rs 8.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 random
23
24
import Const
25
import Rules
26
import Utils
27
28
from ige import GameException
29
from ige import log
30
from ige.IDataHolder import IDataHolder
31
32
33
def makeShipMinSpec(player, name, hullID, eqIDs, improvements,
34
    raiseExs = True):
35
    ship = makeShipFullSpec(player, name, hullID, eqIDs, improvements, raiseExs)
36
    # make 'real' ship spec
37
    spec = IDataHolder()
38
    spec.type = Const.T_SHIP
39
    spec.name = ship.name
40
    spec.hullID = ship.hullID
41
    spec.level = ship.level
42
    spec.eqIDs = ship.eqIDs
43
    spec.improvements = ship.improvements
44
    spec.combatClass = ship.combatClass
45
    spec.signature = ship.signature
46
    spec.scannerPwr = ship.scannerPwr
47
    spec.speed = ship.speed
48
    spec.battleSpeed = ship.battleSpeed
49
    spec.maxHP = ship.maxHP
50
    spec.shieldHP = ship.shieldHP
51
    spec.combatAtt = ship.combatAtt
52
    spec.combatDef = ship.combatDef
53
    spec.missileDef = ship.missileDef
54
    spec.storEn = ship.storEn
55
    spec.operEn = ship.operEn
56
    spec.buildProd = ship.buildProd
57
    spec.buildSRes = ship.buildSRes
58
    spec.weaponIDs = ship.weaponIDs
59
    spec.deployStructs = ship.deployStructs
60
    spec.deployHandlers = ship.deployHandlers
61
    spec.built = 0
62
    spec.buildTurns = 1
63
    spec.upgradeTo = 0
64
    spec.isMilitary = ship.isMilitary
65
    spec.baseExp = ship.baseExp
66
    spec.combatPwr = ship.combatPwr
67
    spec.autoRepairFix = ship.autoRepairFix
68
    spec.autoRepairPerc = ship.autoRepairPerc
69
    spec.shieldRechargeFix = ship.shieldRechargeFix
70
    spec.shieldRechargePerc = ship.shieldRechargePerc
71
    spec.hardShield = ship.hardShield
72
    spec.combatAttMultiplier = ship.combatAttMultiplier
73
    spec.damageAbsorb = ship.damageAbsorb
74
    return spec
75
76
def _checkValidity(ship, tech, installations, equipCounter, raiseExs):
77
    if not raiseExs:
78
        return
79
    # check min hull req
80
    elif tech.minHull > ship.combatClass:
81
        log.warning("Cannot add tech", tech.id, tech.name)
82
        raise GameException("Minimum hull requirement not satisfied.")
83
    # check max hull req
84
    elif tech.maxHull < ship.combatClass:
85
        log.warning("Cannot add tech", tech.id, tech.name)
86
        raise GameException("Maximum hull requirement not satisfied.")
87
    # check maximum installations
88
    elif tech.maxInstallations and installations[tech.id] > tech.maxInstallations:
89
        raise GameException("Maximum number of equipment installations exceeded.")
90
    #check maximum type installations
91
    elif tech.subtype == "seq_mod" and tech.equipType in Rules.maxEquipType:
92
        try:
93
            equipCounter[tech.equipType] += 1
94
        except KeyError:
95
            equipCounter[tech.equipType] = 1
96
        if equipCounter[tech.equipType] > Rules.maxEquipType[tech.equipType]:
97
            raise GameException("Maximum number of restricted type equipment installations exceeded: %s." % tech.equipType)
98
99
def _checkValidityWhole(ship, hull, counter, raiseExs):
100
    if not raiseExs:
101
        return
102
    elif counter.get("seq_ctrl", 0) != 1:
103
        raise GameException("Exactly one control module needs to be in the ship.")
104
    elif ship.slots > hull.slots:
105
        raise GameException("Hull does not have enough slots to hold specified equipment.")
106
    elif ship.weight > hull.maxWeight:
107
        raise GameException("Ship is too heavy.")
108
    elif len(ship.improvements) > Rules.shipMaxImprovements:
109
        raise GameException("Too many improvements.")
110
111
def _moduleSignature(ship, tech):
112
    if tech.signature < 0 and not tech.subtype == "seq_eng":
113
        ship.negsignature = min(tech.signature, ship.negsignature)
114
    else:
115
        ship.signature += tech.signature
116
    ship.minSignature = max(ship.minSignature, tech.minSignature)
117
    ship.signatureCloak = min(ship.signatureCloak, tech.signatureCloak)
118
    ship.signatureDecloak = min(ship.signatureDecloak, tech.signatureDecloak)
119
120
def _finalizeSignature(ship, hull):
121
    ship.signature += ship.negsignature
122
    ship.signature *= ship.signatureCloak * ship.signatureDecloak
123
    ship.signature = int(ship.signature)
124
    ship.signature = max(hull.minSignature, ship.signature, ship.minSignature)
125
126
def _moduleCombat(ship, tech, techEff, combatExtra):
127
    combatExtra += tech.addMP
128
    if tech.subtype == "seq_mod": #not cumulative for equipment; pick best
129
        ship.combatAtt = max(ship.combatAtt, tech.combatAtt * techEff)
130
        ship.combatDef = max(ship.combatDef, tech.combatDef * techEff)
131
        ship.missileDef = max(ship.missileDef, tech.missileDef * techEff)
132
    else:
133
        ship.combatDefBase += tech.combatDef * techEff
134
        ship.missileDefBase += tech.missileDef * techEff
135
        ship.combatAttBase += tech.combatAtt * techEff
136
    #not cumulative; pick best
137
    ship.combatAttMultiplier = max(ship.combatAttMultiplier, (tech.combatAttPerc-1.0) * techEff + 1.0)
138
    ship.combatDefMultiplier = max(ship.combatDefMultiplier, (tech.combatDefPerc-1.0) * techEff + 1.0)
139
    ship.missileDefMultiplier = max(ship.missileDefMultiplier, (tech.missileDefPerc-1.0) * techEff + 1.0)
140
    # if weapon - register only
141
    if tech.subtype == "seq_wpn":
142
        ship.weaponIDs.append(tech.id)
143
        ship.isMilitary = 1
144
        weapon = Rules.techs[tech.id]
145
        ship.baseExp += (weapon.weaponDmgMin + weapon.weaponDmgMax) / 2 * weapon.weaponROF
146
147
def _finalizeCombat(ship):
148
    # compute base attack/defence
149
    ship.combatAtt += ship.combatAttBase
150
    ship.combatAtt += int(ship.battleSpeed)
151
    ship.combatDef = int((ship.combatDef + ship.combatDefBase) * ship.combatDefMultiplier)
152
    ship.missileDef = int((ship.missileDef + ship.missileDefBase) * ship.missileDefMultiplier)
153
    ship.combatDef += int(ship.battleSpeed)
154
    ship.missileDef += int(ship.battleSpeed / 2.0)
155
    for i in ship.improvements:
156
        if i == Const.SI_ATT:
157
            ship.combatAtt *= Rules.shipImprovementMod
158
        elif i == Const.SI_DEF:
159
            ship.combatDef *= Rules.shipImprovementMod
160
            ship.missileDef *= Rules.shipImprovementMod
161
    ship.combatAtt = int(ship.combatAtt / (ship.combatClass + 1.0))
162
    ship.combatDef = int(ship.combatDef / (ship.combatClass + 1.0))
163
    ship.missileDef = int(ship.missileDef / (ship.combatClass + 1.0))
164
    ship.baseExp = int(ship.baseExp * Rules.shipBaseExpMod) + Rules.shipBaseExp[ship.combatClass]
165
166
def _moduleSturdiness(ship, tech, techEff, shieldPerc):
167
    ship.maxHP += tech.maxHP * techEff
168
    shieldPerc = max(shieldPerc, tech.shieldPerc * techEff)
169
    ship.autoRepairFix = max(ship.autoRepairFix, tech.autoRepairFix * techEff)
170
    ship.autoRepairPerc = max(ship.autoRepairPerc, tech.autoRepairPerc * techEff)
171
    ship.shieldRechargeFix = max(ship.shieldRechargeFix, tech.shieldRechargeFix * techEff)
172
    ship.shieldRechargePerc = max(ship.shieldRechargePerc, tech.shieldRechargePerc * techEff)
173
    ship.hardShield = max(ship.hardShield,tech.hardShield * techEff)
174
    ship.damageAbsorb = min(ship.damageAbsorb + tech.damageAbsorb, Rules.maxDamageAbsorb)
175
176
def _finalizeSturdiness(ship, shieldPerc):
177
    # improvements
178
    for i in ship.improvements:
179
        if i == Const.SI_HP:
180
            ship.maxHP *= Rules.shipImprovementMod
181
        elif i == Const.SI_SHIELDS:
182
            ship.shieldHP *= Rules.shipImprovementMod
183
    # round values down
184
    ship.maxHP = int(ship.maxHP)
185
    ship.shieldHP = int(ship.maxHP * shieldPerc)
186
    ship.hardShield = min(1.0,ship.hardShield) #don't allow this to be more than 100% blocking!!
187
188
def _moduleDeployables(ship, tech):
189
    if tech.unpackStruct != Const.OID_NONE:
190
        ship.deployStructs.append(tech.unpackStruct)
191
    if tech.deployHandlerID != Const.OID_NONE:
192
        #this calls another tech at execute time, so only need the ID
193
        ship.deployHandlers.append(tech.deployHandlerID)
194
195
def _moduleBase(ship, tech, techEff):
196
    ship.level = max(ship.level, tech.level)
197
    ship.buildProd += tech.buildProd
198
    ship.buildSRes = Utils.dictAddition(ship.buildSRes, tech.buildSRes)
199
    ship.slots += tech.slots
200
    ship.weight += tech.weight
201
    ship.storEn += tech.storEn * techEff
202
    ship.operEn += tech.operEn
203
    ship.engPwr += tech.engPwr * techEff
204
    ship.engStlPwr += tech.engStlPwr * techEff
205
    ship.scannerPwr = max(ship.scannerPwr, tech.scannerPwr * techEff)
206
207
def _finalizeBase(ship, hull):
208
    ship.weight = max(ship.weight, int(hull.weight / 2.0))
209
    ship.slots = max(ship.slots, 1)
210
    ship.storEn = int(ship.storEn)
211
    ship.scannerPwr = int(ship.scannerPwr)
212
    ship.engPwr = int(ship.engPwr)
213
    ship.engStlPwr = int(ship.engStlPwr)
214
    ship.speed = float(ship.engPwr) / ship.weight
215
    ship.battleSpeed = float(ship.engPwr + ship.engStlPwr) / ship.weight
216
    # improvements
217
    for i in ship.improvements:
218
        if i == Const.SI_SPEED:
219
            ship.speed *= Rules.shipImprovementMod
220
            ship.battleSpeed *= Rules.shipImprovementMod
221
        elif i == Const.SI_TANKS:
222
            ship.storEn *= Rules.shipImprovementMod
223
224
def _setCombatPower(ship, combatExtra):
225
    attackPwr = 0.0
226
    refDefence = 10.0
227
    refAttack = 10.0
228
    refDmg = 10.0
229
    refSpeed = 5.0 #average speed of medium and large hulls
230
    for weaponID in ship.weaponIDs:
231
        weapon = Rules.techs[weaponID]
232
        dmg = (weapon.weaponDmgMin + weapon.weaponDmgMax) / 2 * weapon.weaponROF
233
        att = int((ship.combatAtt + weapon.weaponAtt) * ship.combatAttMultiplier)
234
        sizeCompensation = max(1, weapon.weaponClass - 1) # reduce torps and bombs
235
        attackPwr += (att / float(att + refDefence) * dmg) / sizeCompensation
236
    hpEffect = ship.maxHP + ship.shieldHP
237
    attDefEffect = refAttack / (refAttack + ship.combatDef) * refDmg
238
    speedEffect = min(1.33, max(0.5, (ship.battleSpeed / refSpeed)))
239
    combatExtra += ship.damageAbsorb * 1500
240
    ship.combatPwr = int(attackPwr * hpEffect / attDefEffect * speedEffect + combatExtra)
241
242
def makeShipFullSpec(player, name, hullID, eqIDs, improvements, raiseExs = True):
243
    if not hullID:
244
        raise GameException("Ship's hull must be specified.")
245
    hull = Rules.techs[hullID]
246
    if not hull.isShipHull:
247
        raise GameException("Ship's hull must be specified.")
248
    ship = IDataHolder()
249
    ship.type = Const.T_SHIP
250
    # initial values
251
    hullTechEff = Rules.techImprEff[player.techs.get(hullID, Rules.techBaseImprovement)]
252
    ship.name = name
253
    ship.hullID = hullID
254
    ship.eqIDs = eqIDs
255
    ship.level = hull.level
256
    ship.combatClass = hull.combatClass
257
    ship.improvements = improvements
258
    ship.buildProd = hull.buildProd
259
    ship.buildSRes = copy.copy(hull.buildSRes)
260
    # stats grouped as "Base"
261
    ship.operEn = hull.operEn
262
    ship.storEn = hull.storEn * hullTechEff
263
    ship.weight = hull.weight
264
    ship.slots = 0
265
    ship.scannerPwr = max(hull.scannerPwr * hullTechEff, Rules.scannerMinPwr)
266
    ship.engPwr = 0
267
    ship.engStlPwr = 0
268
    ship.speed = 0.0
269
    ship.battleSpeed = 0.0
270
    # stats grouped as "Signature"
271
    ship.signature = hull.signature
272
    ship.negsignature = 0
273
    ship.minSignature = hull.minSignature
274
    ship.signatureCloak = 1.0
275
    ship.signatureDecloak = 1.0
276
    # stats grouped as "Combat"
277
    ship.combatAttBase = hull.combatAtt * hullTechEff
278
    ship.combatAtt = 0
279
    ship.combatAttMultiplier = 1.0
280
    ship.combatDefBase = hull.combatDef * hullTechEff
281
    ship.combatDef = 0
282
    ship.combatDefMultiplier = 1.0
283
    ship.missileDefBase = hull.missileDef * hullTechEff
284
    ship.missileDef = 0
285
    ship.missileDefMultiplier = 1.0
286
    ship.weaponIDs = []
287
    ship.isMilitary = 0
288
    ship.baseExp = 0
289
    combatExtra = 0
290
    # stats grouped as "Sturdiness"
291
    ship.autoRepairFix = hull.autoRepairFix
292
    ship.autoRepairPerc = hull.autoRepairPerc
293
    ship.shieldRechargeFix = hull.shieldRechargeFix
294
    ship.shieldRechargePerc = hull.shieldRechargePerc
295
    ship.hardShield = 0.0
296
    ship.shieldHP = 0
297
    ship.maxHP = int(hull.maxHP * hullTechEff)
298
    ship.damageAbsorb = 0
299
    shieldPerc = 0.0
300
    # stats grouped as "Deployables"
301
    ship.deployStructs = []
302
    ship.deployHandlers = []
303
304
    ship.upgradeTo = 0
305
    counter = {}
306
    installations = {}
307
    equipCounter = {}
308
    for techID in eqIDs:
309
        tech = Rules.techs[techID]
310
        techEff = Rules.techImprEff[player.techs.get(techID, Rules.techBaseImprovement)]
311
        if eqIDs[techID] < 0 and raiseExs:
312
            raise GameException("Invalid equipment count (less than 0).")
313
        for i in xrange(0, eqIDs[techID]):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
314
            counter[tech.subtype] = 1 + counter.get(tech.subtype, 0)
315
            installations[techID] = 1 + installations.get(techID, 0)
316
            _checkValidity(ship, tech, installations, equipCounter, raiseExs)
317
            # add values
318
319
            _moduleBase(ship, tech, techEff)
320
            _moduleSignature(ship, tech)
321
            _moduleCombat(ship, tech, techEff, combatExtra)
322
            _moduleSturdiness(ship, tech, techEff, shieldPerc)
323
            _moduleDeployables(ship, tech)
324
325
    _checkValidityWhole(ship, hull, counter, raiseExs)
326
    _finalizeBase(ship, hull)
327
    _finalizeSignature(ship, hull)
328
    _finalizeCombat(ship)
329
    _finalizeSturdiness(ship, shieldPerc)
330
    _setCombatPower(ship, combatExtra)
331
    return ship
332
333
# ROF tables
334
rofTable = {}
335
for i in xrange(0, 100):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable xrange does not seem to be defined.
Loading history...
336
    line = []
337
    level = i
338
    for j in xrange(0, 100):
339
        if level >= 100:
340
            line.append(1)
341
            level -= 100
342
        else:
343
            line.append(0)
344
        level += i
345
    # check
346
    sum = 0
347
    for e in line:
348
        sum += e
349
    #@log.debug("ROF %02d %2.2f" % (i, sum / 10.0), line)
350
    assert i == sum, "Bad ROF table sum for %d" % i
351
    rofTable[i] = line
352
353
def getRounds(rof, counter):
354
    rof = int(rof * 100)
355
    return rof / 100 + rofTable[rof % 100][counter % 100]
356
357
# damage
358
def computeDamage(wpnCls, trgtCls, dmgMin, dmgMax, weaponEff):
359
    """Compute damage that causes weapon to the target with specified combat
360
       class."""
361
    assert trgtCls >= wpnCls
362
    dmgMin = int(dmgMin*weaponEff)
363
    dmgMax = int(dmgMax*weaponEff)
364
    dmg = 1.0 * random.randint(dmgMin, dmgMax) * Rules.weaponDmgDegrade[trgtCls - wpnCls]
365
    intDmg = int(round(dmg,0))
366
    return intDmg
367
368
def sortShips(ships):
369
    # TODO: remove in 0.6
370
    origShips = ships[:]
371
372
    # split them
373
    types = {}
374
    for ship in ships:
375
        t = ship[Const.SHIP_IDX_DESIGNID]
376
        if t not in types:
377
            types[t] = []
378
        types[t].append(ship)
379
380
    # sort them by HP, init counter
381
    incrs = {}
382
    counters = {}
383
    for t in types:
384
        # take shield into account
385
        types[t].sort(key=lambda a: a[Const.SHIP_IDX_HP] + a[Const.SHIP_IDX_SHIELDHP])
386
        incrs[t] = 1.0 / (float(len(types[t])) / len(ships))
387
        counters[t] = incrs[t]
388
389
    # rearrange them
390
    ships = []
391
392
    while types:
393
        # find minimum
394
        minCounter = 1e100
395
        minType = None
396
        for t in counters:
397
            if minCounter > counters[t]:
398
                minType = t
399
                minCounter = counters[t]
400
        # pick ship, increase counter
401
        ships.append(types[minType].pop(0))
402
        counters[minType] += incrs[minType]
403
        if not types[minType]:
404
            del types[minType]
405
            del counters[minType]
406
407
    # check result
408
    # TODO: remove in 0.6
409
    for ship in ships:
410
        origShips.remove(ship)
411
    assert origShips == []
412
413
    return ships
414