예제 #1
0
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, {})
예제 #2
0
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
예제 #3
0
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)
예제 #4
0
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
예제 #5
0
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