-
Notifications
You must be signed in to change notification settings - Fork 439
Expand file tree
/
Copy pathmodifiedAttributeDict.py
More file actions
681 lines (595 loc) · 28.2 KB
/
modifiedAttributeDict.py
File metadata and controls
681 lines (595 loc) · 28.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
# ===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
# ===============================================================================
from collections.abc import MutableMapping
from copy import copy
from math import exp
from eos.const import Operator
# TODO: This needs to be moved out, we shouldn't have *ANY* dependencies back to other modules/methods inside eos.
# This also breaks writing any tests. :(
from eos.db.gamedata.queries import getAttributeInfo
defaultValuesCache = {}
cappingAttrKeyCache = {}
resistanceCache = {}
_debug_max_velocity_count = 0
def getAttrDefault(key, fallback=None):
try:
default = defaultValuesCache[key]
except KeyError:
attrInfo = getAttributeInfo(key)
if attrInfo is None:
default = defaultValuesCache[key] = None
else:
default = defaultValuesCache[key] = attrInfo.defaultValue
if default is None:
default = fallback
return default
def getResistanceAttrID(modifyingItem, effect):
# If it doesn't exist on the effect, check the modifying module's attributes.
# If it's there, cache it and return
if effect.resistanceID:
return effect.resistanceID
cacheKey = (modifyingItem.item.ID, effect.ID)
try:
return resistanceCache[cacheKey]
except KeyError:
attrPrefix = effect.getattr('prefix')
if attrPrefix:
resistanceID = int(modifyingItem.getModifiedItemAttr('{}ResistanceID'.format(attrPrefix))) or None
if not resistanceID:
resistanceID = int(modifyingItem.getModifiedItemAttr('{}RemoteResistanceID'.format(attrPrefix))) or None
else:
resistanceID = int(modifyingItem.getModifiedItemAttr("remoteResistanceID")) or None
resistanceCache[cacheKey] = resistanceID
return resistanceID
class ItemAttrShortcut:
def getModifiedItemAttr(self, key, default=0):
return_value = self.itemModifiedAttributes.get(key)
return return_value if return_value is not None else default
def getModifiedItemAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
return_value = self.itemModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
return return_value if return_value is not None else default
def getItemBaseAttrValue(self, key, default=0):
return_value = self.itemModifiedAttributes.getOriginal(key)
return return_value if return_value is not None else default
class ChargeAttrShortcut:
def getModifiedChargeAttr(self, key, default=0):
return_value = self.chargeModifiedAttributes.get(key)
return return_value if return_value is not None else default
def getModifiedChargeAttrExtended(self, key, extraMultipliers=None, ignoreAfflictors=(), default=0):
return_value = self.chargeModifiedAttributes.getExtended(key, extraMultipliers=extraMultipliers, ignoreAfflictors=ignoreAfflictors)
return return_value if return_value is not None else default
def getChargeBaseAttrValue(self, key, default=0):
return_value = self.chargeModifiedAttributes.getOriginal(key)
return return_value if return_value is not None else default
class ModifiedAttributeDict(MutableMapping):
overrides_enabled = False
class CalculationPlaceholder:
def __init__(self):
pass
def __init__(self, fit=None, parent=None):
self.__fit = fit
self.parent = parent
# Stores original values of the entity
self.__original = None
# Modified values during calculations
self.__intermediary = {}
# Final modified values
self.__modified = {}
# Affected by entities
# Format:
# {attr name: {modifying fit: (
# modifying item, operation, stacking group, pre-resist amount,
# post-resist amount, affects result or not)}}
self.__affectedBy = {}
# Overrides (per item)
self.__overrides = {}
# Mutators (per module)
self.__mutators = {}
# Dictionaries for various value modification types
self.__forced = {}
self.__preAssigns = {}
self.__preIncreases = {}
self.__multipliers = {}
self.__penalizedMultipliers = {}
self.__postIncreases = {}
# We sometimes override the modifier (for things like skill handling). Store it here instead of registering it
# with the fit (which could cause bug for items that have both item bonuses and skill bonus, ie Subsystems)
self.__tmpModifier = None
def clear(self):
self.__intermediary.clear()
self.__modified.clear()
self.__affectedBy.clear()
self.__forced.clear()
self.__preAssigns.clear()
self.__preIncreases.clear()
self.__multipliers.clear()
self.__penalizedMultipliers.clear()
self.__postIncreases.clear()
@property
def fit(self):
# self.fit is usually set during fit calculations when the item is registered with the fit. However,
# under certain circumstances, an effect will not work as it will try to modify an item which has NOT
# yet been registered and thus has not had self.fit set. In this case, use the modules owner attribute
# to point to the correct fit. See GH Issue #434
if self.__fit is not None:
return self.__fit
if hasattr(self.parent, 'owner'):
return self.parent.owner
return None
@fit.setter
def fit(self, fit):
self.__fit = fit
@property
def original(self):
return self.__original
@original.setter
def original(self, val):
self.__original = val
self.__modified.clear()
@property
def overrides(self):
return self.__overrides
@overrides.setter
def overrides(self, val):
self.__overrides = val
@property
def mutators(self):
return {x.attribute.name: x for x in self.__mutators.values()}
@mutators.setter
def mutators(self, val):
self.__mutators = val
def __getitem__(self, key):
# Check if we have final calculated value
val = self.__modified.get(key)
if val is self.CalculationPlaceholder:
val = self.__modified[key] = self.__calculateValue(key)
if val is not None:
return val
# Then in values which are not yet calculated
if self.__intermediary:
val = self.__intermediary.get(key)
else:
val = None
if val is not None:
return val
# Original value is the least priority
return self.getOriginal(key)
def getExtended(self, key, extraMultipliers=None, ignoreAfflictors=None, default=0):
"""
Here we consider couple of parameters. If they affect final result, we do
not store result, and if they are - we do.
"""
# Here we do not have support for preAssigns/forceds, as doing them would
# mean that we have to store all of them in a list which increases memory use,
# and we do not actually need those operators atm
preIncreaseAdjustment = 0
multiplierAdjustment = 1
ignorePenalizedMultipliers = {}
postIncreaseAdjustment = 0
afflictions = self.getAfflictions(key)
ignored_zero_multiplier = False
global _debug_max_velocity_count
if key == 'maxVelocity' and _debug_max_velocity_count < 8:
ignore_names = []
if ignoreAfflictors:
ignore_names = [getattr(getattr(a, 'item', None), 'name', str(a)) for a in ignoreAfflictors]
affliction_names = []
for fit, afflictors in afflictions.items():
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
name = getattr(getattr(afflictor, 'item', None), 'name', str(afflictor))
affliction_names.append((name, operator, stackingGroup, preResAmount, postResAmount, used))
print(
"[AttrDebug] key=maxVelocity ignores={} afflictions={}".format(ignore_names, affliction_names),
flush=True)
_debug_max_velocity_count += 1
for fit, afflictors in afflictions.items():
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
if afflictor in ignoreAfflictors:
if operator == Operator.MULTIPLY:
if stackingGroup is None:
if postResAmount == 0:
ignored_zero_multiplier = True
else:
multiplierAdjustment /= postResAmount
else:
ignorePenalizedMultipliers.setdefault(stackingGroup, []).append(postResAmount)
elif operator == Operator.PREINCREASE:
preIncreaseAdjustment -= postResAmount
elif operator == Operator.POSTINCREASE:
postIncreaseAdjustment -= postResAmount
if ignored_zero_multiplier and self.__multipliers.get(key, 1) == 0:
recomputed_multiplier = 1
for fit, afflictors in afflictions.items():
for afflictor, operator, stackingGroup, preResAmount, postResAmount, used in afflictors:
if operator != Operator.MULTIPLY or stackingGroup is not None:
continue
if afflictor in ignoreAfflictors:
continue
recomputed_multiplier *= postResAmount
# Recalculate value without applying stored zero multiplier
try:
cappingKey = cappingAttrKeyCache[key]
except KeyError:
attrInfo = getAttributeInfo(key)
if attrInfo is None:
cappingId = cappingAttrKeyCache[key] = None
else:
cappingId = attrInfo.maxAttributeID
if cappingId is None:
cappingKey = None
else:
cappingAttrInfo = getAttributeInfo(cappingId)
cappingKey = None if cappingAttrInfo is None else cappingAttrInfo.name
cappingAttrKeyCache[key] = cappingKey
if cappingKey:
cappingValue = self[cappingKey]
cappingValue = cappingValue.value if hasattr(cappingValue, "value") else cappingValue
else:
cappingValue = None
preIncrease = self.__preIncreases.get(key, 0)
postIncrease = self.__postIncreases.get(key, 0)
penalizedMultiplierGroups = self.__penalizedMultipliers.get(key, {})
if extraMultipliers is not None:
penalizedMultiplierGroups = copy(penalizedMultiplierGroups)
for stackGroup, operationsData in extraMultipliers.items():
multipliers = []
for mult, resAttrID in operationsData:
if not resAttrID:
multipliers.append(mult)
continue
resAttrInfo = getAttributeInfo(resAttrID)
if not resAttrInfo:
multipliers.append(mult)
continue
resMult = self.fit.ship.itemModifiedAttributes[resAttrInfo.attributeName]
if resMult is None or resMult == 1:
multipliers.append(mult)
continue
mult = (mult - 1) * resMult + 1
multipliers.append(mult)
penalizedMultiplierGroups[stackGroup] = penalizedMultiplierGroups.get(stackGroup, []) + multipliers
default = getAttrDefault(key, fallback=0.0)
val = self.__intermediary.get(key, self.__preAssigns.get(key, self.getOriginal(key, default)))
val += preIncrease
if preIncreaseAdjustment is not None:
val += preIncreaseAdjustment
val *= recomputed_multiplier
for penaltyGroup, penalizedMultipliers in penalizedMultiplierGroups.items():
if ignorePenalizedMultipliers is not None and penaltyGroup in ignorePenalizedMultipliers:
penalizedMultipliers = penalizedMultipliers[:]
for ignoreMult in ignorePenalizedMultipliers[penaltyGroup]:
try:
penalizedMultipliers.remove(ignoreMult)
except ValueError:
pass
l1 = [_val for _val in penalizedMultipliers if _val > 1]
l2 = [_val for _val in penalizedMultipliers if _val < 1]
abssort = lambda _val: -abs(_val - 1)
l1.sort(key=abssort)
l2.sort(key=abssort)
for l in (l1, l2):
for i in range(len(l)):
bonus = l[i]
val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
val += postIncrease
if postIncreaseAdjustment is not None:
val += postIncreaseAdjustment
if cappingValue is not None:
val = min(val, cappingValue)
if key in ("cpu", "power", "cpuOutput", "powerOutput"):
val = round(val, 2)
return val
# If we apply no customizations - use regular getter
if (
not extraMultipliers and
preIncreaseAdjustment == 0 and multiplierAdjustment == 1 and
postIncreaseAdjustment == 0 and len(ignorePenalizedMultipliers) == 0
):
return self.get(key, default=default)
# Try to calculate custom values
val = self.__calculateValue(
key, extraMultipliers=extraMultipliers, preIncAdj=preIncreaseAdjustment, multAdj=multiplierAdjustment,
postIncAdj=postIncreaseAdjustment, ignorePenMult=ignorePenalizedMultipliers)
if val is not None:
return val
# Then the same fallbacks as in regular getter
if self.__intermediary:
val = self.__intermediary.get(key)
else:
val = None
if val is not None:
return val
val = self.getOriginal(key)
if val is not None:
return val
return default
def __delitem__(self, key):
if key in self.__modified:
del self.__modified[key]
if key in self.__intermediary:
del self.__intermediary[key]
def getOriginal(self, key, default=None):
val = None
if self.overrides_enabled and self.overrides:
val = self.overrides.get(key, val)
# mutators are overriden by overrides. x_x
val = self.mutators.get(key, val)
if val is None:
if self.original:
val = self.original.get(key, val)
if val is None:
val = getAttrDefault(key, fallback=None)
if val is None and val != default:
val = default
return val.value if hasattr(val, "value") else val
def __setitem__(self, key, val):
self.__intermediary[key] = val
def __iter__(self):
all_dict = dict(self.original, **self.__modified)
return (key for key in all_dict)
def __contains__(self, key):
return (self.original is not None and key in self.original) or \
key in self.__modified or key in self.__intermediary
def __placehold(self, key):
"""Create calculation placeholder in item's modified attribute dict"""
self.__modified[key] = self.CalculationPlaceholder
def __len__(self):
keys = set()
keys.update(iter(self.original.keys()))
keys.update(iter(self.__modified.keys()))
keys.update(iter(self.__intermediary.keys()))
return len(keys)
def __calculateValue(self, key, extraMultipliers=None, preIncAdj=None, multAdj=None, postIncAdj=None, ignorePenMult=None):
# It's possible that various attributes are capped by other attributes,
# it's defined by reference maxAttributeID
try:
cappingKey = cappingAttrKeyCache[key]
except KeyError:
attrInfo = getAttributeInfo(key)
if attrInfo is None:
cappingId = cappingAttrKeyCache[key] = None
else:
cappingId = attrInfo.maxAttributeID
if cappingId is None:
cappingKey = None
else:
cappingAttrInfo = getAttributeInfo(cappingId)
cappingKey = None if cappingAttrInfo is None else cappingAttrInfo.name
cappingAttrKeyCache[key] = cappingKey
if cappingKey:
cappingValue = self[cappingKey]
cappingValue = cappingValue.value if hasattr(cappingValue, "value") else cappingValue
else:
cappingValue = None
# If value is forced, we don't have to calculate anything,
# just return forced value instead
force = self.__forced[key] if key in self.__forced else None
if force is not None:
if cappingValue is not None:
force = min(force, cappingValue)
if key in ("cpu", "power", "cpuOutput", "powerOutput"):
force = round(force, 2)
return force
# Grab our values if they're there, otherwise we'll take default values
preIncrease = self.__preIncreases.get(key, 0)
multiplier = self.__multipliers.get(key, 1)
penalizedMultiplierGroups = self.__penalizedMultipliers.get(key, {})
# Add extra multipliers to the group, not modifying initial data source
if extraMultipliers is not None:
penalizedMultiplierGroups = copy(penalizedMultiplierGroups)
for stackGroup, operationsData in extraMultipliers.items():
multipliers = []
for mult, resAttrID in operationsData:
if not resAttrID:
multipliers.append(mult)
continue
resAttrInfo = getAttributeInfo(resAttrID)
if not resAttrInfo:
multipliers.append(mult)
continue
resMult = self.fit.ship.itemModifiedAttributes[resAttrInfo.attributeName]
if resMult is None or resMult == 1:
multipliers.append(mult)
continue
mult = (mult - 1) * resMult + 1
multipliers.append(mult)
penalizedMultiplierGroups[stackGroup] = penalizedMultiplierGroups.get(stackGroup, []) + multipliers
postIncrease = self.__postIncreases.get(key, 0)
# Grab initial value, priorities are:
# Results of ongoing calculation > preAssign > original > 0
default = getAttrDefault(key, fallback=0.0)
val = self.__intermediary.get(key, self.__preAssigns.get(key, self.getOriginal(key, default)))
# We'll do stuff in the following order:
# preIncrease > multiplier > stacking penalized multipliers > postIncrease
val += preIncrease
if preIncAdj is not None:
val += preIncAdj
val *= multiplier
if multAdj is not None:
val *= multAdj
# Each group is penalized independently
# Things in different groups will not be stack penalized between each other
for penaltyGroup, penalizedMultipliers in penalizedMultiplierGroups.items():
if ignorePenMult is not None and penaltyGroup in ignorePenMult:
# Avoid modifying source and remove multipliers we were asked to remove for this calc
penalizedMultipliers = penalizedMultipliers[:]
for ignoreMult in ignorePenMult[penaltyGroup]:
try:
penalizedMultipliers.remove(ignoreMult)
except ValueError:
pass
# A quick explanation of how this works:
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
l1 = [_val for _val in penalizedMultipliers if _val > 1]
l2 = [_val for _val in penalizedMultipliers if _val < 1]
# 2: The most significant bonuses take the smallest penalty,
# This means we'll have to sort
abssort = lambda _val: -abs(_val - 1)
l1.sort(key=abssort)
l2.sort(key=abssort)
# 3: The first module doesn't get penalized at all
# Any module after the first takes penalties according to:
# 1 + (multiplier - 1) * math.exp(- math.pow(i, 2) / 7.1289)
for l in (l1, l2):
for i in range(len(l)):
bonus = l[i]
val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
val += postIncrease
if postIncAdj is not None:
val += postIncAdj
# Cap value if we have cap defined
if cappingValue is not None:
val = min(val, cappingValue)
if key in ("cpu", "power", "cpuOutput", "powerOutput"):
val = round(val, 2)
return val
def __handleSkill(self, skillName):
"""
Since ship skill bonuses do not directly modify the attributes, it does
not register as an affector (instead, the ship itself is the affector).
To fix this, we pass the skill which ends up here, where we register it
with the fit and thus get the correct affector. Returns skill level to
be used to modify modifier. See GH issue #101
"""
skill = self.fit.character.getSkill(skillName)
self.__tmpModifier = skill
return skill.level
def getAfflictions(self, key):
return self.__affectedBy.get(key, {})
def iterAfflictions(self):
return self.__affectedBy.__iter__()
def __afflict(self, attributeName, operator, stackingGroup, preResAmount, postResAmount, used=True):
"""Add modifier to list of things affecting current item"""
# Do nothing if no fit is assigned
fit = self.fit
if fit is None:
return
# Create dictionary for given attribute and give it alias
if attributeName not in self.__affectedBy:
self.__affectedBy[attributeName] = {}
affs = self.__affectedBy[attributeName]
origin = fit.getOrigin()
fit = origin if origin and origin != fit else fit
# If there's no set for current fit in dictionary, create it
if fit not in affs:
affs[fit] = []
# Reassign alias to list
affs = affs[fit]
# Get modifier which helps to compose 'Affected by' map
if self.__tmpModifier:
modifier = self.__tmpModifier
self.__tmpModifier = None
else:
modifier = fit.getModifier()
# Add current affliction to list
affs.append((modifier, operator, stackingGroup, preResAmount, postResAmount, used))
def preAssign(self, attributeName, value, **kwargs):
"""Overwrites original value of the entity with given one, allowing further modification"""
self.__preAssigns[attributeName] = value
self.__placehold(attributeName)
self.__afflict(attributeName, Operator.PREASSIGN, None, value, value, value != self.getOriginal(attributeName))
def increase(self, attributeName, increase, position="pre", skill=None, **kwargs):
"""Increase value of given attribute by given number"""
if skill:
increase *= self.__handleSkill(skill)
if 'effect' in kwargs:
increase *= ModifiedAttributeDict.getResistance(self.fit, kwargs['effect']) or 1
# Increases applied before multiplications and after them are
# written in separate maps
if position == "pre":
operator = Operator.PREINCREASE
tbl = self.__preIncreases
elif position == "post":
operator = Operator.POSTINCREASE
tbl = self.__postIncreases
else:
raise ValueError("position should be either pre or post")
if attributeName not in tbl:
tbl[attributeName] = 0
tbl[attributeName] += increase
self.__placehold(attributeName)
self.__afflict(attributeName, operator, None, increase, increase, increase != 0)
def multiply(self, attributeName, multiplier, stackingPenalties=False, penaltyGroup="default", skill=None, **kwargs):
"""Multiply value of given attribute by given factor"""
if multiplier is None: # See GH issue 397
return
if skill:
multiplier *= self.__handleSkill(skill)
preResMultiplier = multiplier
resisted = False
# Goddammit CCP, make up your mind where you want this information >.< See #1139
if 'effect' in kwargs:
resistFactor = ModifiedAttributeDict.getResistance(self.fit, kwargs['effect']) or 1
if resistFactor != 1:
resisted = True
multiplier = (multiplier - 1) * resistFactor + 1
# If we're asked to do stacking penalized multiplication, append values
# to per penalty group lists
if stackingPenalties:
if attributeName not in self.__penalizedMultipliers:
self.__penalizedMultipliers[attributeName] = {}
if penaltyGroup not in self.__penalizedMultipliers[attributeName]:
self.__penalizedMultipliers[attributeName][penaltyGroup] = []
tbl = self.__penalizedMultipliers[attributeName][penaltyGroup]
tbl.append(multiplier)
# Non-penalized multiplication factors go to the single list
else:
if attributeName not in self.__multipliers:
self.__multipliers[attributeName] = 1
self.__multipliers[attributeName] *= multiplier
self.__placehold(attributeName)
afflictPenal = ""
if stackingPenalties:
afflictPenal += "s"
if resisted:
afflictPenal += "r"
self.__afflict(
attributeName, Operator.MULTIPLY, penaltyGroup if stackingPenalties else None,
preResMultiplier, multiplier, multiplier != 1)
def boost(self, attributeName, boostFactor, skill=None, **kwargs):
"""Boost value by some percentage"""
if skill:
boostFactor *= self.__handleSkill(skill)
# We just transform percentage boost into multiplication factor
self.multiply(attributeName, 1 + boostFactor / 100.0, **kwargs)
def force(self, attributeName, value, **kwargs):
"""Force value to attribute and prohibit any changes to it"""
self.__forced[attributeName] = value
self.__placehold(attributeName)
self.__afflict(attributeName, Operator.FORCE, None, value, value)
@staticmethod
def getResistance(fit, effect):
# Resistances are applicable only to projected effects
if isinstance(effect.type, (tuple, list)):
effectType = effect.type
else:
effectType = (effect.type,)
if 'projected' not in effectType:
return 1
remoteResistID = getResistanceAttrID(modifyingItem=fit.getModifier(), effect=effect)
if not remoteResistID:
return 1
attrInfo = getAttributeInfo(remoteResistID)
# Get the attribute of the resist
resist = fit.ship.itemModifiedAttributes[attrInfo.attributeName] or None
return resist or 1
class Affliction:
def __init__(self, affliction_type, amount):
self.type = affliction_type
self.amount = amount