Completed
Pull Request — master (#223)
by Marek
01:55
created

ige.ospace.ShipUtils._moduleSturdiness()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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