-
Notifications
You must be signed in to change notification settings - Fork 0
/
applyActions.py
347 lines (335 loc) · 17.1 KB
/
applyActions.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
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
# -*- coding: utf-8 -*-
"""
Created on Tue May 31 16:57:03 2016
@author: rmondoncancel
"""
from stateManagement import \
getBuff, applyBuff, removeBuff, applyDebuff, getResistance, addAction, \
nextAction, applyDamage, applyTpChange
from dpsCalculation import \
baseDamage, basePotency, critChance, critBonus, gcdTick, dotTick, \
strBonus, buffedPotency, getWeaponDelay
from priorityManagement import actionToGcdType
from buffs import b
from debuffs import d
import random
import copy
TIME_EPSILON = 0.000000001
def comboBonus(state, skill) :
newSkill = copy.deepcopy(skill)
if 'combo' in newSkill and state['timeline']['lastGCD'] == newSkill['combo'][0] :
for bonus in newSkill['combo'][1] :
newSkill[bonus] = newSkill['combo'][1][bonus]
return newSkill
def specialAction(state, skill) :
"""Modify the state if the skill has a 'special' property
The special property contains a key, and this function modifies the state
according to what is supposed to happen when this specific key is found.
"""
newState = copy.deepcopy(state)
# Returns the state is no special property is present
if 'special' not in skill :
return newState
# MNK
# Add an action to remove all forms at the end of perfectBalance
if skill['special'] == 'removeForms' :
newState = addAction(newState, b(state['player']['class'])[skill['addBuff'][0]]['duration'], { 'type': 'special', 'name': skill['special'] })
# Switch to next form when using formShift
elif skill['special'] == 'nextForm' :
forms = [ 'opoOpoForm', 'raptorForm', 'coerlForm' ]
if forms[0] in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
newState = removeBuff(newState, forms)
newState = applyBuff(newState, b(state['player']['class'])[forms[1]])
elif forms[1] in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
newState = removeBuff(newState, forms)
newState = applyBuff(newState, b(state['player']['class'])[forms[2]])
elif forms[2] in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
newState = removeBuff(newState, forms)
newState = applyBuff(newState, b(state['player']['class'])[forms[0]])
else :
newState = applyBuff(newState, b(state['player']['class'])[forms[0]])
# Add skill buff for bootshine to have 100% crit chance if in opoOpoForm
elif skill['special'] == 'bootshineCrit' :
if 'opoOpoForm' in [ pb[0]['name'] for pb in newState['player']['buff'] ] or 'perfectBalance' in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
newState = applyBuff(newState, b(state['player']['class'])[skill['special']])
# Apply dragonKick debuff at the end of the skill if in opoOpoForm
elif skill['special'] == 'dragonKick' :
if 'opoOpoForm' in [ pb[0]['name'] for pb in newState['player']['buff'] ] or 'perfectBalance' in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
newState = addAction(newState, 0, { 'type': 'special', 'name': skill['special'] })
# DGN
elif skill['special'] == 'procBloodOfTheDragon' :
if 'bloodOfTheDragon' in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
if random.random() < 0.5 :
newState = addAction(newState, 0, { 'type': 'special', 'name': 'sharperFangAndClaw' })
else :
newState = addAction(newState, 0, { 'type': 'special', 'name': 'enhancedWheelingThrust' })
elif skill['special'] == 'surgeBonus' :
if 'powerSurge' in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
newState = applyBuff(newState, b(state['player']['class'])['surgeBonus'])
elif skill['special'] == 'extendBloodOfTheDragon' :
if 'bloodOfTheDragon' in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
currentTimestamp = min( na[0] for na in newState['timeline']['nextActions'] if na[1] == { 'name': 'bloodOfTheDragon', 'type': 'removeBuff' } )
newState['timeline']['nextActions'] = [ na for na in newState['timeline']['nextActions'] if na[1] != { 'name': 'bloodOfTheDragon', 'type': 'removeBuff' } ]
newState = addAction(newState, min(30, currentTimestamp + 15), { 'name': 'bloodOfTheDragon', 'type': 'removeBuff' })
elif skill['special'] == 'reduceBloodOfTheDragon' :
if 'bloodOfTheDragon' in [ pb[0]['name'] for pb in newState['player']['buff'] ] :
currentTimestamp = min( na[0] for na in newState['timeline']['nextActions'] if na[1] == { 'name': 'bloodOfTheDragon', 'type': 'removeBuff' } )
newState['timeline']['nextActions'] = [ na for na in newState['timeline']['nextActions'] if na[1] != { 'name': 'bloodOfTheDragon', 'type': 'removeBuff' } ]
newState = addAction(newState, max(0, currentTimestamp - 10), { 'name': 'bloodOfTheDragon', 'type': 'removeBuff' })
return newState
def applySpecialAction(state, name) :
"""Resolve special actions created by special mechanics in the
specialAction function if the said mechanic require an application delay
"""
newState = copy.deepcopy(state)
# MNK
# Remove all forms at the end on perfectBalance
if name == 'removeForms' :
forms = [ 'opoOpoForm', 'raptorForm', 'coerlForm' ]
newState = removeBuff(newState, forms)
# Apply dragonKick debuff right after dragonKick is cast
elif name == 'dragonKick' :
newState = applyDebuff(newState, d(state['player']['class'])[name])
# DRG
elif name == 'sharperFangAndClaw' :
newState = applyBuff(newState, b(state['player']['class'])[name])
elif name == 'enhancedWheelingThrust' :
newState = applyBuff(newState, b(state['player']['class'])[name])
newState = nextAction(newState)
return (newState, {})
def computeDamage(state, src, value = None) :
"""Calculates the damage for a given source src
From a certain state, gives the damage output of an auto-attack, a direct
skill or a DoT tick.
src value is either 'autoAttack', 'skill' or 'DoT'.
value is the applied skill or DoT if applicable.
"""
if src == 'autoAttack' :
pot = 100 * state['player']['baseStats']['weaponDelay'] / 3
dtype = state['player']['baseStats']['weaponType']
elif src == 'skill' :
pot = value['potency']
dtype = state['player']['baseStats']['weaponType']
elif src == 'DoT':
pot = value['potency']
dtype = 'DoT'
wd = state['player']['baseStats']['weaponDamage']
st = state['player']['baseStats']['strength']
det = state['player']['baseStats']['determination']
crt = state['player']['baseStats']['criticalHitRate']
# Get buffs
potBuf = getBuff(state, 'potency')
dmgBuf = getBuff(state, 'damage')
crtBuf = getBuff(state, 'critChance')
stBuff = getBuff(state, 'strength')
buffedSt = strBonus(stBuff, st)
pot = buffedPotency(pot, potBuf)
# Base damage and crit
if src == 'DoT' :
# Add skill speed bonus to damage if DoT
ss = state['player']['baseStats']['skillSpeed']
ssBuf = dotTick(ss)
baseDmg = baseDamage(pot, wd, buffedSt, det, dmgBuf + [ ssBuf ])
basePot = basePotency(pot, dmgBuf + [ ssBuf ])
else :
baseDmg = baseDamage(pot, wd, buffedSt, det, dmgBuf)
basePot = basePotency(pot, dmgBuf)
crtChc = critChance(crt, crtBuf)
crtBonF = critBonus(crt)
# Average damage with crit
crtDmg = baseDmg * (1 + crtChc * crtBonF)
crtPot = basePot * (1 + crtChc * crtBonF)
# Apply resistance
if dtype == 'DoT':
dmgRes = 1
else :
dmgRes = getResistance(state, dtype)
effDmg = crtDmg * dmgRes
effPot = crtPot * dmgRes
# Calculates hit and crit damages
hitDmg = baseDmg * dmgRes
critDmg = baseDmg * (1 + crtBonF) * dmgRes
hitPot = basePot * dmgRes
critPot = basePot * (1 + crtBonF) * dmgRes
return (effDmg, effPot, hitDmg, hitPot, critDmg, critPot, crtChc, crtBonF)
def applyAutoAttack(state) :
"""Apply auto attack to a state.
This function takes a given state that should have an autoAttack current
action and calculates damage output of the attack.
Then the function creates the next autoAttack action and returns the couple
(newState, result) with newState the state at the following action.
"""
newState = copy.deepcopy(state)
# Prevents auto-attack from happening if on prepull
if any(state['timeline']['prepull'].values()) :
autoAttackDelay = newState['player']['baseStats']['weaponDelay']
newState = addAction(newState, autoAttackDelay, { 'type': 'autoAttack' })
return(nextAction(newState), {})
# Computes auto-attack damage
(effDmg, effPot, hitDmg, hitPot, critDmg, critPot, crtChc, crtBonF) = computeDamage(newState, 'autoAttack')
result = {
'damage': effDmg,
'potency': effPot,
'hitDamage': hitDmg,
'critDamage': critDmg,
'hitPotency': hitPot,
'critPotency': critPot,
'critChance': crtChc,
'critBonus': crtBonF,
'type': 'autoAttack',
'timestamp': newState['timeline']['timestamp'],
}
# Reduce HP of target
newState = applyDamage(newState, effDmg)
# Add next auto-attck
speedBuf = getBuff(newState, 'speed')
autoAttackDelay = getWeaponDelay(newState['player']['baseStats']['weaponDelay'], speedBuf)
newState = addAction(newState, autoAttackDelay, { 'type': 'autoAttack' })
newState = nextAction(newState)
return (newState, result)
def applySkill(state, skill) :
"""Apply instant or gcd skill to a state.
This function takes a given state that should have a gcdSkill or
instantSkill current action and calculates the new state after the skill
is casted.
Then the function creates the next skill actions and returns the couple
(newState, result) with newState the state at the following action.
"""
newState = copy.deepcopy(state)
# Solve the state if the priority list does not return any possible skill
# for the current state
if skill == None:
# If still in prepull, add to the state that the prepull does not
# require any addition gcd/instant skill at the current state
if any(newState['timeline']['prepull'].values()) :
newState['timeline']['prepull'][actionToGcdType(newState['timeline']['currentAction']['type'])] = False
# If instant is still to try for prepull but a GCD has jsut been used,
# Add an instant to try just after
if newState['timeline']['prepull']['instant'] and newState['timeline']['currentAction']['type'] == 'gcdSkill':
newState = addAction(newState, 0, { 'type': 'instantSkill' })
newState = addAction(newState, TIME_EPSILON, { 'type': 'gcdSkill' })
# If at the end of the prepull, add a new gcdSkill to start the
# rotation
if not any(newState['timeline']['prepull'].values()) and newState['timeline']['currentAction']['type'] == 'gcdSkill':
newState = addAction(newState, 0, { 'type': 'gcdSkill' })
elif newState['timeline']['currentAction']['type'] == 'gcdSkill' :
nextTpTick = min( na[0] for na in newState['timeline']['nextActions'] if na[1]['type'] == 'tpTick' ) - state['timeline']['timestamp']
newState = addAction(newState, 0, { 'type': 'instantSkill' })
newState = addAction(newState, nextTpTick + TIME_EPSILON, { 'type': 'gcdSkill' })
newState = nextAction(newState)
return (newState, {})
# Apply combo bonus if applicable
skill = comboBonus(newState, skill)
# Resolve special action of the current skill if applicable
newState = specialAction(newState, skill)
newState = applyTpChange(newState, - skill['tpCost'])
# Get result if skill is a damaging skill and we are not in prepull
if 'potency' in skill and not any(newState['timeline']['prepull'].values()) :
(effDmg, effPot, hitDmg, hitPot, critDmg, critPot, crtChc, crtBonF) = computeDamage(newState, 'skill', skill)
result = {
'damage': effDmg,
'potency': effPot,
'hitDamage': hitDmg,
'critDamage': critDmg,
'hitPotency': hitPot,
'critPotency': critPot,
'critChance': crtChc,
'critBonus': crtBonF,
'source': skill['name'],
'type': 'skill',
'timestamp': newState['timeline']['timestamp'],
'tpSpent': skill['tpCost'],
}
# Reduce HP of target
newState = applyDamage(newState, effDmg)
# Get result if skill is not a damaging skill
else :
result = {
'source': skill['name'],
'type': 'skill',
'timestamp': newState['timeline']['timestamp'],
'tpSpent': skill['tpCost'],
}
# Apply buff/debuff modifications for the current skill
if 'removeBuff' in skill :
newState = removeBuff(newState, skill['removeBuff'])
if 'addBuff' in skill :
for bufName in skill['addBuff'] :
newState = applyBuff(newState, b(state['player']['class'])[bufName])
if 'addDebuff' in skill :
for debufName in skill['addDebuff'] :
newState = applyDebuff(newState, d(state['player']['class'])[debufName])
# Set the current skill on cooldown if applicable
if skill['cooldown'] > 0 :
newState = addAction(newState, skill['cooldown'], { 'type': 'removeCooldown', 'name': skill['name'] })
newState['player']['cooldown'] = newState['player']['cooldown'] + [ skill['name'] ]
# Get gcd duration for next GCD
ss = newState['player']['baseStats']['skillSpeed']
ssBuf = getBuff(newState, 'speed')
gcdDuration = gcdTick(ss, ssBuf)
# Continue prepull if on prepull and a valid skill is found
if any(newState['timeline']['prepull'].values()) :
newState['timeline']['prepull']['global'] = True
newState['timeline']['prepull']['instant'] = True
newState['timeline']['prepullTimestamp'][skill['gcdType']] = newState['timeline']['timestamp'] + skill['animationLock']
# Add an instant skill and a GCD skill if current skill is a GCD skill
if skill['gcdType'] == 'global' :
# Saves last GCD skill for combos
newState['timeline']['lastGCD'] = skill['name']
# Remove next gcdSkills and instantSkills to avoid overlaps
newState['timeline']['nextActions'] = [ na for na in newState['timeline']['nextActions'] if na[1]['type'] != 'gcdSkill' and na[1]['type'] != 'instantSkill' ]
newState = addAction(newState, skill['animationLock'], { 'type': 'instantSkill' })
newState = addAction(newState, gcdDuration * (skill['gcdModifier'] if 'gcdModifier' in skill else 1), { 'type': 'gcdSkill' })
# Add following actions if skill is an instant skill
if skill['gcdType'] == 'instant' :
# Remove next instantSkills to avoid overlaps
newState['timeline']['nextActions'] = [ na for na in newState['timeline']['nextActions'] if na[1]['type'] != 'instantSkill' ]
newState = addAction(newState, skill['animationLock'], { 'type': 'instantSkill' })
# Delay next GCD skill if animation lock pushes it
nextGcdTimestamp = min( na[0] for na in newState['timeline']['nextActions'] if na[1]['type'] == 'gcdSkill' )
if nextGcdTimestamp < newState['timeline']['timestamp'] + skill['animationLock']:
newState['timeline']['nextActions'] = [ na for na in newState['timeline']['nextActions'] if na[1]['type'] != 'gcdSkill' ]
newState = addAction(newState, skill['animationLock'] + TIME_EPSILON, { 'type': 'gcdSkill' })
newState = nextAction(newState)
return (newState, result)
def applySingleDot(state, dot) :
"""Calculates the damage output of a single DoT
Returns the couple (newState, result) with newState the state at the
following action.
"""
# Calaculates the damage of the current DoT
(effDmg, effPot, hitDmg, hitPot, critDmg, critPot, crtChc, crtBonF) = computeDamage(dot['snapshot'], 'DoT', dot['props'])
result = {
'damage': effDmg,
'potency': effPot,
'hitDamage': hitDmg,
'critDamage': critDmg,
'hitPotency': hitPot,
'critPotency': critPot,
'critChance': crtChc,
'critBonus': crtBonF,
'source': dot['name'],
'type': 'DoT',
'timestamp': state['timeline']['timestamp'],
}
# Reduce HP of target
newState = applyDamage(state, effDmg)
newState = nextAction(newState)
return (newState, result)
def applyDot(state) :
"""Create actions for each DoT applied to the target
Add a dot action for each currently applied DoT to the target when the
global DoT tick happens
Then creates the next global DoT tick and returns the couple
(newState, result) with newState the state at the following action.
"""
# List of currently applied DoTs
dotList = [ d for d in state['enemy']['debuff'] if d['type'] == 'DoT' ]
newState = copy.deepcopy(state)
# Add an action for each applied DoT
for d in dotList :
newState = addAction(newState, 0, { 'type': 'dot', 'name': d['name'] })
# Add following global DoT tick
newState = addAction(newState, 3, { 'type': 'dotTick' })
newState = nextAction(newState)
return newState