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 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 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 addHiddenConditions(priorityElement, pClass, useTp): """Add hidden conditions for a priority element * Add a condition on GCD type (global vs instant) to use the correct skills for a given action * Check for required buffs if skill require a given buff to be castable * Check if skill is on cooldown if skill has a cooldown * Prevent instant skills from delaying the GCD * Add additional conditions present in skill description """ # Get the skill matching the priority element skill = s(pClass)[priorityElement['group']][priorityElement['name']] newPriorityElement = copy.deepcopy(priorityElement) # Add GCD type check to condition if 'condition' not in newPriorityElement: # Add a condition key if absent newPriorityElement['condition'] = { 'type': 'gcdType', 'comparison': 'is', 'value': skill['gcdType'] } else: # Add the GCD type check to the existing condition if it already exists newPriorityElement['condition'] = { 'logic': 'and', 'list': [ newPriorityElement['condition'], { 'type': 'gcdType', 'comparison': 'is', 'value': skill['gcdType'] }, ], } # Add required buff condition; required buffs are on 'or', so it will match # if any of the requiredBuff property of skill is present # Also, if the buff has stacks, it will by default check if the buff is # at max stacks rather than just present if 'requiredBuff' in skill: reqBufList = [] # Loop on required buffs for bufName in skill['requiredBuff']: # Check if buffs has stacks if 'maxStacks' in b(pClass)[bufName]: reqBufList = reqBufList + [{ 'type': 'buffAtMaxStacks', 'name': bufName, 'comparison': 'is', 'value': True, }] else: reqBufList = reqBufList + [{ 'type': 'buffPresent', 'name': bufName, 'comparison': 'is', 'value': True, }] newPriorityElement['condition'] = { 'logic': 'and', 'list': [ newPriorityElement['condition'], { 'logic': 'or', 'list': reqBufList, }, ], } # Add a condition to check if skill is on cooldown if it has one if skill['cooldown'] > 0: newPriorityElement['condition'] = { 'logic': 'and', 'list': [ newPriorityElement['condition'], { 'type': 'cooldownPresent', 'name': skill['name'], 'comparison': 'is', 'value': False, }, ], } # Prevent instant skills from delaying GCD by default if skill['gcdType'] == 'instant' and 'prepull' not in newPriorityElement: newPriorityElement['condition'] = { 'logic': 'and', 'list': [ newPriorityElement['condition'], { 'type': 'gcdDelay', 'delay': skill['animationLock'], 'comparison': '<=', 'value': 0, }, ], } # Add skill specific skills if present if 'condition' in skill: newPriorityElement['condition'] = { 'logic': 'and', 'list': [ newPriorityElement['condition'], skill['condition'], ] } if useTp and 'tpCost' in skill: newPriorityElement['condition'] = { 'logic': 'and', 'list': [ newPriorityElement['condition'], { 'type': 'state', 'name': 'tp', 'comparison': '>=', 'value': skill['tpCost'] }, ] } return newPriorityElement
def getConditionValue(state, condition): """Returns the value to check for a given single condition switch on the condition type to return the matching value in the state Current types are: * buffPresent: True if buff is present in state else False * buffAtMaxStacks: True if buff is at max stacks in state else False * buffTimeLeft: time left before the buff drops in state; 0 if absent * debuffPresent: True if debuff is present in state else False * debuffTimeLeft: time left before the debuff drops in state; 0 if absent * cooldownPresent: True if skill is on cooldown in state else False * cooldownTimeLeft: time left before the skill is available again in state; 0 if absent * gcdType: gcdType of current skill (global/instant) * gcdDelay: how much would the skill delay the next GCD if cast * enemy: enemy specific values * lifePercent: percent of life left on the enemy; 100% if time based simulation """ if condition['type'] == 'buffPresent': return condition['name'] in [ bf[0]['name'] for bf in state['player']['buff'] ] elif condition['type'] == 'buffStacks': bufArray = [ bf[1] for bf in state['player']['buff'] if bf[0]['name'] == condition['name'] ] return bufArray[0] if len(bufArray) >= 1 else 0 elif condition['type'] == 'buffAtMaxStacks': return condition['name'] in [ bf[0]['name'] for bf in state['player']['buff'] if 'maxStacks' in b(state['player']['class'])[bf[0]['name']] and bf[1] == b(state['player']['class'])[bf[0]['name']]['maxStacks'] ] elif condition['type'] == 'buffTimeLeft': timers = [ na[0] - state['timeline']['timestamp'] for na in state['timeline']['nextActions'] if na[1] == { 'type': 'removeBuff', 'name': condition['name'] } ] if len(timers) == 0: return 0 return min(timers) elif condition['type'] == 'debuffPresent': return condition['name'] in [ d['name'] for d in state['enemy']['debuff'] ] elif condition['type'] == 'debuffTimeLeft': timers = [ na[0] - state['timeline']['timestamp'] for na in state['timeline']['nextActions'] if na[1] == { 'type': 'removeDebuff', 'name': condition['name'] } ] if len(timers) == 0: return 0 return min(timers) elif condition['type'] == 'cooldownPresent': return condition['name'] in state['player']['cooldown'] elif condition['type'] == 'cooldownTimeLeft': timers = [ na[0] - state['timeline']['timestamp'] for na in state['timeline']['nextActions'] if na[1] == { 'type': 'removeCooldown', 'name': condition['name'] } ] if len(timers) == 0: return 0 return min(timers) elif condition['type'] == 'gcdType': return actionToGcdType(state['timeline']['currentAction']['type']) elif condition['type'] == 'gcdDelay': if state['timeline']['currentAction']['type'] == 'gcdSkill': return condition['delay'] nextGcdTimestamp = min(na[0] for na in state['timeline']['nextActions'] if na[1]['type'] == 'gcdSkill') return max( 0, state['timeline']['timestamp'] + condition['delay'] - nextGcdTimestamp) elif condition['type'] == 'state': if condition['name'] == 'enemyLifePercent': if 'hp' not in state['enemy'] or 'maxHp' not in state['enemy']: return 100 return 100 * state['enemy']['hp'] / state['enemy']['maxHp'] elif condition['name'] == 'lastGCD': return state['timeline']['lastGCD'] if 'lastGCD' in state[ 'timeline'] else None elif condition['name'] == 'tp': return state['player']['tp'] if 'tp' in state['player'] else 1000