forked from pyfa-org/Pyfa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
modifiedAttributeDict.py
executable file
·261 lines (230 loc) · 10.5 KB
/
modifiedAttributeDict.py
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
#===============================================================================
# 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 math import exp
import collections
defaultValuesCache = {}
class ItemAttrShortcut(object):
def getModifiedItemAttr(self, key):
if key in self.itemModifiedAttributes:
return self.itemModifiedAttributes[key]
else:
return None
class ChargeAttrShortcut(object):
def getModifiedChargeAttr(self, key):
if key in self.chargeModifiedAttributes:
return self.chargeModifiedAttributes[key]
else:
return None
class ModifiedAttributeDict(collections.MutableMapping):
class CalculationPlaceholder():
pass
def __init__(self, fit = None):
self.fit = fit
# Stores original values of the entity
self.__original = None
# Modified values during calculations
self.__intermediary = {}
# Final modified values
self.__modified = {}
# Affected by entities
self.__affectedBy = {}
# Dictionaries for various value modification types
self.__forced = {}
self.__preAssigns = {}
self.__preIncreases = {}
self.__multipliers = {}
self.__penalizedMultipliers = {}
self.__postIncreases = {}
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 original(self):
return self.__original
@original.setter
def original(self, val):
self.__original = val
self.__modified.clear()
def __getitem__(self, key):
# Check if we have final calculated value
if key in self.__modified:
if self.__modified[key] == self.CalculationPlaceholder:
self.__modified[key] = self.__calculateValue(key)
return self.__modified[key]
# Then in values which are not yet calculated
elif key in self.__intermediary:
return self.__intermediary[key]
# Original value is the least priority
else:
return self.getOriginal(key)
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):
val = self.__original.get(key)
if val is None:
return None
return val.value if hasattr(val, "value") else val
def __setitem__(self, key, val):
self.__intermediary[key] = val
def __iter__(self):
all = dict(self.__original, **self.__modified)
return (key for key in all)
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(self.__original.iterkeys())
keys.update(self.__modified.iterkeys())
keys.update(self.__intermediary.iterkeys())
return len(keys)
def __calculateValue(self, key):
# 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:
return force
# Grab our values if they're there, otherwise we'll take default values
preIncrease = self.__preIncreases[key] if key in self.__preIncreases else 0
multiplier = self.__multipliers[key] if key in self.__multipliers else 1
penalizedMultiplierGroups = self.__penalizedMultipliers[key] if key in self.__penalizedMultipliers else {}
postIncrease = self.__postIncreases[key] if key in self.__postIncreases else 0
# Grab initial value, priorities are:
# Results of ongoing calculation > preAssign > original > 0
try:
default = defaultValuesCache[key]
except KeyError:
from eos.db.gamedata.queries import getAttributeInfo
attrInfo = getAttributeInfo(key)
if attrInfo is None:
default = defaultValuesCache[key] = 0.0
else:
dv = attrInfo.defaultValue
default = defaultValuesCache[key] = dv if dv is not None else 0.0
val = self.__intermediary[key] if key in self.__intermediary else self.__preAssigns[key] if key in self.__preAssigns else self.getOriginal(key) if key in self.__original else default
# We'll do stuff in the following order:
# preIncrease > multiplier > stacking penalized multipliers > postIncrease
val += preIncrease
val *= multiplier
# Each group is penalized independently
# Things in different groups will not be stack penalized between each other
for penalizedMultipliers in penalizedMultiplierGroups.itervalues():
# A quick explanation of how this works:
# 1: Bonuses and penalties are calculated seperately, so we'll have to filter each of them
l1 = filter(lambda val: val > 1, penalizedMultipliers)
l2 = filter(lambda val: val < 1, penalizedMultipliers)
# 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 xrange(len(l)):
bonus = l[i]
val *= 1 + (bonus - 1) * exp(- i ** 2 / 7.1289)
val += postIncrease
return val
def getAfflictions(self, key):
return self.__affectedBy[key] if key in self.__affectedBy else {}
def iterAfflictions(self):
return self.__affectedBy.__iter__()
def __afflict(self, attributeName, operation, bonus, used=True):
"""Add modifier to list of things affecting current item"""
# Do nothing if no fit is assigned
if self.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]
# If there's no set for current fit in dictionary, create it
if self.fit not in affs:
affs[self.fit] = set()
# Reassign alias to set
affs = affs[self.fit]
# Get modifier which helps to compose 'Affected by' map
modifier = self.fit.getModifier()
# Add current affliction to set
affs.add((modifier, operation, bonus, used))
def preAssign(self, attributeName, value):
"""Overwrites original value of the entity with given one, allowing further modification"""
self.__preAssigns[attributeName] = value
self.__placehold(attributeName)
self.__afflict(attributeName, "=", value, value != self.getOriginal(attributeName))
def increase(self, attributeName, increase, position="pre"):
"""Increase value of given attribute by given number"""
# Increases applied before multiplications and after them are
# written in separate maps
if position == "pre":
tbl = self.__preIncreases
elif position == "post":
tbl = self.__postIncreases
else:
raise ValueError("position should be either pre or post")
if not attributeName in tbl:
tbl[attributeName] = 0
tbl[attributeName] += increase
self.__placehold(attributeName)
self.__afflict(attributeName, "+", increase, increase != 0)
def multiply(self, attributeName, multiplier, stackingPenalties=False, penaltyGroup="default"):
"""Multiply value of given attribute by given factor"""
# If we're asked to do stacking penalized multiplication, append values
# to per penalty group lists
if stackingPenalties:
if not attributeName in self.__penalizedMultipliers:
self.__penalizedMultipliers[attributeName] = {}
if not penaltyGroup 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 not attributeName in self.__multipliers:
self.__multipliers[attributeName] = 1
self.__multipliers[attributeName] *= multiplier
self.__placehold(attributeName)
self.__afflict(attributeName, "%s*" % ("s" if stackingPenalties else ""), multiplier, multiplier != 1)
def boost(self, attributeName, boostFactor, *args, **kwargs):
"""Boost value by some percentage"""
# We just transform percentage boost into multiplication factor
self.multiply(attributeName, 1 + boostFactor / 100.0, *args, **kwargs)
def force(self, attributeName, value):
"""Force value to attribute and prohibit any changes to it"""
self.__forced[attributeName] = value
self.__placehold(attributeName)
self.__afflict(attributeName, u"\u2263", value)
class Affliction():
def __init__(self, type, amount):
self.type = type
self.amount = amount