from templeplus.pymod import PythonModifier
from toee import *
import tpdp
import logbook
import roll_history

debug_enabled = False

def debug_print(*args):
    if debug_enabled:
        for arg in args:
            print arg,
    return

def handle_sanctuary(to_hit_eo, d20a):
    tgt = d20a.target
    if tgt == OBJ_HANDLE_NULL or not tgt.is_critter():
        return
    if d20a.query_can_be_affected_action_perform(tgt):
        return
    flags = to_hit_eo.attack_packet.get_flags()
    if flags & D20CAF_CRITICAL:
        flags &= ~D20CAF_CRITICAL
    if flags & D20CAF_HIT:
        flags &= ~D20CAF_HIT
        to_hit_eo.bonus_list.add_zeroed(262) # Action lost due to Sanctuary
    to_hit_eo.attack_packet.set_flags(flags)
    return


def add_percent_chance_history_stub():
    return


def mirror_image_attack_roll(d20a):
    performer = d20a.performer
    target = d20a.target

    # Work out current Dex bonus to AC
    mi_ac_evt_obj = tpdp.EventObjAttack()
    mi_ac_evt_obj.attack_packet.attacker = performer
    mi_ac_evt_obj.attack_packet.target = target
    flags = d20a.flags
    flags |= D20CAF_TOUCH_ATTACK
    mi_ac_evt_obj.attack_packet.set_flags(flags)
    mi_ac_evt_obj.attack_packet.action_type = d20a.action_type
    mi_ac_evt_obj.dispatch(target, OBJ_HANDLE_NULL, ET_OnGetAC, EK_NONE)

    full_bonus = mi_ac_evt_obj.bonus_list
    tgt_ac = full_bonus.get_sum()

    dex = target.stat_level_get(stat_dexterity)
    dex_mod = - (dex-10)/2
    full_bonus.modify(dex_mod, 3, 104)
    tgt_ac_mod = full_bonus.get_sum()

    dex_ac_bonus = tgt_ac - tgt_ac_mod

    size_offset = target.stat_level_get(stat_size) - STAT_SIZE_MEDIUM
    size_bonus = 0
    if size_offset < 0:
        size_offset = - size_offset
        size_bonus = 1 << (size_offset-1)
    elif size_offset > 0:
        size_bonus = (-1) << (size_offset-1)

    image_bonus = tpdp.BonusList()
    image_bonus.add(10, 1, 102)
    image_bonus.add(dex_ac_bonus, 3, 104)
    image_bonus.add(size_bonus, 0, 115)
    image_ac = image_bonus.get_sum()

    #Performer to Hit Bonus
    to_hit = tpdp.EventObjAttack()
    to_hit.dispatch(performer, OBJ_HANDLE_NULL, ET_OnToHitBonus2, EK_NONE)

    to_hit_dice = dice_new("1d20")
    to_hit_roll = to_hit_dice.roll()
    to_hit_bonus = to_hit.bonus_list.get_sum()

    roll_id = tpdp.create_history_attack_roll(
            performer, target, to_hit_roll, to_hit.bonus_list,
            image_bonus, to_hit.attack_packet.get_flags())
    result = to_hit_roll - image_ac + to_hit_bonus
    d20a.roll_id_0 = roll_id
    return result

def hitMirrorImage(d20a, numberOfMirrorImages):
    #Check if real target was hit
    #A roll of 1 indicates hit on real target
    mirrorDice = dice_new("1d{}".format(numberOfMirrorImages+1) )
    mirrorRoll = mirrorDice.roll()
    if mirrorRoll == 1:
        return False
    
    performer = d20a.performer
    target = d20a.target
    
    #Get spell_id and spellName
    spell_id = target.d20_query_get_data(Q_Critter_Has_Mirror_Image,0)
    
    roll_result = mirror_image_attack_roll(d20a)
    if roll_result >= 0:
        target.d20_send_signal(S_Spell_Mirror_Image_Struck, spell_id, 0)
        target.float_mesfile_line('mes\\combat.mes', 109)
        game.create_history_from_pattern(10, performer, target)

    return True

def getDefenderConcealment(d20a):
    target = d20a.target
    defenderConcealment = tpdp.EventObjAttack()
    defenderConcealment.attack_packet.set_flags(d20a.flags)
    defenderConcealment.attack_packet.target = target
    defenderConcealment.attack_packet.attacker = d20a.performer
    return defenderConcealment.dispatch(target, OBJ_HANDLE_NULL, ET_OnGetDefenderConcealmentMissChance, EK_NONE)

def getAttackerConcealment(performer):
    performerConcealment = tpdp.EventObjObjectBonus()
    performerConcealment.dispatch(performer, ET_OnGetAttackerConcealmentMissChance, EK_NONE)
    return performerConcealment.bonus_list.get_highest()

def getSuppressConcealment(performer, target):
    #suppressingConditions can be easily expanded with new conditions if necessary
    suppressingConditions = [tpdp.get_condition_ref("sp-True Strike"), tpdp.get_condition_ref("Weapon Seeking")]
    if any(performer.d20_query_with_data(Q_Critter_Has_Condition, conRef, 0) for conRef in suppressingConditions):
        return True
    elif performer.can_blindsee(target):
        return True
    elif performer.d20_query("Ignore Concealment"): #Example for Arcane Archer; not implemented in AA
        return True
    return False

def rollConcealment(concealmentMissChance):
    concealmentDice = dice_new("1d100")
    concealmentDiceRoll = concealmentDice.roll()
    if concealmentDiceRoll > concealmentMissChance:
        return True, concealmentDiceRoll
    return False, concealmentDiceRoll

def toHitResult(performerToHit, targetAc):
    toHitDice = dice_new("1d20")
    toHitRoll = toHitDice.roll()
    if toHitRoll == 1:
        return False, toHitRoll
    elif toHitRoll == 20:
        return True, toHitRoll
    elif toHitRoll + performerToHit >= targetAc:
        return True, toHitRoll
    return False, toHitRoll


def to_hit_processing(d20a):
    performer = d20a.performer #auto performer = d20a.d20APerformer;
    d20Data = d20a.data1 #auto d20Data = d20a.data1;
    target = d20a.target #auto tgt = d20a.d20ATarget;
    if not target:
        return

    #Mirror Image
    numberOfMirrorImages = target.d20_query(Q_Critter_Has_Mirror_Image)
    if numberOfMirrorImages:
        if hitMirrorImage(d20a, numberOfMirrorImages):
            return

    #Concealment
    debug_print("Concealment")
    targetConcealment = getDefenderConcealment(d20a)
    performerCanSuppressConcealment = getSuppressConcealment(performer, target)
    if performerCanSuppressConcealment:
        targetConcealment = 0
    concealmentMissChance = max(targetConcealment, getAttackerConcealment(performer))
    if concealmentMissChance > 0:
        is_success, miss_chance_roll = rollConcealment(concealmentMissChance)
        if is_success:
            roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 60, miss_chance_roll, 194, 193)
            d20a.roll_id_1 = roll_id
        else: # concealment miss
            roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 60, miss_chance_roll, 195, 193)
            d20a.roll_id_1 = roll_id

            # Blind fight - give second chance
            if not performer.has_feat(feat_blind_fight):
                return

            is_success, miss_chance_roll = rollConcealment(concealmentMissChance)
            if not is_success:
                roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 61, miss_chance_roll, 195, 193)
                return
            
            roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 61, miss_chance_roll, 194, 193)
            d20a.roll_id_2 = roll_id

    #ToHitBonus Actions
    debug_print("To Hit")
    to_hit_eo = tpdp.EventObjAttack()
    to_hit_eo.attack_packet.set_flags(d20a.flags)
    to_hit_eo.attack_packet.target = target
    to_hit_eo.attack_packet.action_type = d20a.action_type #dispIoToHitBon.attackPacket.d20ActnType = d20a.action_type
    to_hit_eo.attack_packet.attacker = performer
    to_hit_eo.attack_packet.event_key = d20Data #dispIoToHitBon.attackPacket.dispKey = d20Data
    unarmed = OBJ_HANDLE_NULL
    if to_hit_eo.attack_packet.get_flags() & D20CAF_TOUCH_ATTACK:
        to_hit_eo.attack_packet.set_weapon_used(unarmed)
    elif to_hit_eo.attack_packet.get_flags() & D20CAF_SECONDARY_WEAPON:
        offhandItem = performer.item_worn_at(item_wear_weapon_secondary)
        if offhandItem == OBJ_HANDLE_NULL or offhandItem.type != obj_t_weapon:
            to_hit_eo.attack_packet.set_weapon_used(unarmed)
        else:
            to_hit_eo.attack_packet.set_weapon_used(offhandItem)
    else:
        mainhandItem = performer.item_worn_at(item_wear_weapon_primary)
        if mainhandItem == OBJ_HANDLE_NULL or mainhandItem.type != obj_t_weapon:
            to_hit_eo.attack_packet.set_weapon_used(unarmed)
        else:
            to_hit_eo.attack_packet.set_weapon_used(mainhandItem)
    to_hit_eo.attack_packet.ammo_item = performer.get_ammo_used()
    flags = to_hit_eo.attack_packet.get_flags()
    flags |= D20CAF_FINAL_ATTACK_ROLL
    to_hit_eo.attack_packet.set_flags(flags)
    to_hit_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnGetBucklerAcPenalty , EK_NONE)
    to_hit_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnToHitBonus2, EK_NONE) # // note: the "Global" condition has ToHitBonus2 hook that dispatches the ToHitBonusBase
    to_hit_bon_final = to_hit_eo.dispatch(target, OBJ_HANDLE_NULL, ET_OnToHitBonusFromDefenderCondition, EK_NONE)

    #targetAc Actions
    debug_print("Target AC")
    target_ac_eo = to_hit_eo.__copy__()
    target_ac_eo.bonus_list.reset()
    
    target_ac_eo.dispatch(target, OBJ_HANDLE_NULL, ET_OnGetAC, EK_NONE)
    tgt_ac_final = target_ac_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnGetAcModifierFromAttacker, EK_NONE)

    #Check if attacks hits
    attackDidHit, toHitRoll = toHitResult(to_hit_bon_final, tgt_ac_final)

    critAlwaysCheat = cheats.critical #Note: changed behavior from vanilla (this used to toggle the property)

    #Check for special hit conditions
    if not attackDidHit:
        if to_hit_eo.attack_packet.get_flags() & D20CAF_ALWAYS_HIT:
            attackDidHit = True
        elif critAlwaysCheat:
            attackDidHit = True
        else:
            #Reroll Check
            if performer.d20_query(Q_RerollAttack):
                tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags()  )
                rerollDidHit, toHitRoll = toHitResult(to_hit_bon_final, tgt_ac_final)
                flags = to_hit_eo.attack_packet.get_flags()
                flags |= D20CAF_REROLL
                to_hit_eo.attack_packet.set_flags(flags)
                if not rerollDidHit:
                    logbook.inc_misses(performer)
                else:
                    attackDidHit = True

    if not attackDidHit:
        debug_print("Missed")
        roll_id = tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags()  )
        d20a.roll_id_0 = roll_id
        return

    #We have a hit sir!
    debug_print("Scored a hit")
    flags = to_hit_eo.attack_packet.get_flags()
    flags |= D20CAF_HIT
    to_hit_eo.attack_packet.set_flags(flags)
    logbook.inc_hits(performer)
    

    #Check if attack was a critical hit
    performerCritRange = to_hit_eo.__copy__()
    performerCritRange.bonus_list.reset()
    critRange = 21 - performerCritRange.dispatch(performer, OBJ_HANDLE_NULL, ET_OnGetCriticalHitRange, EK_NONE)

    if target.d20_query(Q_Critter_Is_Immune_Critical_Hits):
        isCritical = False
    elif toHitRoll == 20:
        isCritical = True
    elif toHitRoll >= critRange:
        isCritical = True
    elif critAlwaysCheat:
        isCritical = True
    else:
        isCritical = False

    #Check to Confirm Critical Hit
    crit_hit_roll = -1
    if isCritical:
        debug_print("Confirm critical:")
        to_hit_bon_final += to_hit_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnConfirmCriticalBonus, EK_NONE)
        critConfirmed, crit_hit_roll = toHitResult(to_hit_bon_final, tgt_ac_final)

        #Check for special confirm conditions
        if not critConfirmed:
            if performer.d20_query("Always Confirm Criticals"):
                critConfirmed = True
            elif critAlwaysCheat:
                critConfirmed = True
            else:
                if performer.d20_query(Q_RerollCritical):
                    tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags(), crit_hit_roll  )
                    critConfirmed, crit_hit_roll = toHitResult(to_hit_bon_final, tgt_ac_final)
                    #no reroll flag seems to be added in original code

        if critConfirmed:
            debug_print("Crit confirm")
            flags = to_hit_eo.attack_packet.get_flags()
            flags |= D20CAF_CRITICAL
            to_hit_eo.attack_packet.set_flags(flags)
    
    #Deflect Arrows
    #Unsure why it is done after confirm crit,
    #If done before, history window for normal attack
    #could be done earlier
    #dispIoToHitBon.Dispatch(dispIoToHitBon.attackPacket.victim, objHndl::null, dispTypeDeflectArrows, DK_NONE)
    #unsure why it is not simply tgt, will copy it
    to_hit_eo.dispatch(to_hit_eo.attack_packet.target, OBJ_HANDLE_NULL, ET_OnDeflectArrows, EK_NONE)

    handle_sanctuary(to_hit_eo, d20a)
    
    #Set flags
    debug_print("Final")
    d20a.flags = to_hit_eo.attack_packet.get_flags()
    roll_id = tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags(), crit_hit_roll  )
    d20a.roll_id_0 = roll_id
    return

from templeplus.pymod import PythonModifier
from toee import *
import tpdp
import logbook
import roll_history

debug_enabled = False

def debug_print(*args):
    if debug_enabled:
        for arg in args:
            print arg,
    return

def handle_sanctuary(to_hit_eo, d20a):
    tgt = d20a.target
    if tgt == OBJ_HANDLE_NULL or not tgt.is_critter():
        return
    if d20a.query_can_be_affected_action_perform(tgt):
        return
    flags = to_hit_eo.attack_packet.get_flags()
    if flags & D20CAF_CRITICAL:
        flags &= ~D20CAF_CRITICAL
    if flags & D20CAF_HIT:
        flags &= ~D20CAF_HIT
        to_hit_eo.bonus_list.add_zeroed(262) # Action lost due to Sanctuary
    to_hit_eo.attack_packet.set_flags(flags)
    return


def add_percent_chance_history_stub():
    return


def mirror_image_attack_roll(d20a):
    performer = d20a.performer
    target = d20a.target

    # Work out current Dex bonus to AC
    mi_ac_evt_obj = tpdp.EventObjAttack()
    mi_ac_evt_obj.attack_packet.attacker = performer
    mi_ac_evt_obj.attack_packet.target = target
    flags = d20a.flags
    flags |= D20CAF_TOUCH_ATTACK
    mi_ac_evt_obj.attack_packet.set_flags(flags)
    mi_ac_evt_obj.attack_packet.action_type = d20a.action_type
    mi_ac_evt_obj.dispatch(target, OBJ_HANDLE_NULL, ET_OnGetAC, EK_NONE)

    full_bonus = mi_ac_evt_obj.bonus_list
    tgt_ac = full_bonus.get_sum()

    dex = target.stat_level_get(stat_dexterity)
    dex_mod = - (dex-10)/2
    full_bonus.modify(dex_mod, 3, 104)
    tgt_ac_mod = full_bonus.get_sum()

    dex_ac_bonus = tgt_ac - tgt_ac_mod

    size_offset = target.stat_level_get(stat_size) - STAT_SIZE_MEDIUM
    size_bonus = 0
    if size_offset < 0:
        size_offset = - size_offset
        size_bonus = 1 << (size_offset-1)
    elif size_offset > 0:
        size_bonus = (-1) << (size_offset-1)

    image_bonus = tpdp.BonusList()
    image_bonus.add(10, 1, 102)
    image_bonus.add(dex_ac_bonus, 3, 104)
    image_bonus.add(size_bonus, 0, 115)
    image_ac = image_bonus.get_sum()

    #Performer to Hit Bonus
    to_hit = tpdp.EventObjAttack()
    to_hit.dispatch(performer, OBJ_HANDLE_NULL, ET_OnToHitBonus2, EK_NONE)

    to_hit_dice = dice_new("1d20")
    to_hit_roll = to_hit_dice.roll()
    to_hit_bonus = to_hit.bonus_list.get_sum()

    roll_id = tpdp.create_history_attack_roll(
            performer, target, to_hit_roll, to_hit.bonus_list,
            image_bonus, to_hit.attack_packet.get_flags())
    result = to_hit_roll - image_ac + to_hit_bonus
    d20a.roll_id_0 = roll_id
    return result

def hitMirrorImage(d20a, numberOfMirrorImages):
    #Check if real target was hit
    #A roll of 1 indicates hit on real target
    mirrorDice = dice_new("1d{}".format(numberOfMirrorImages+1) )
    mirrorRoll = mirrorDice.roll()
    if mirrorRoll == 1:
        return False
    
    performer = d20a.performer
    target = d20a.target
    
    #Get spell_id and spellName
    spell_id = target.d20_query_get_data(Q_Critter_Has_Mirror_Image,0)
    
    roll_result = mirror_image_attack_roll(d20a)
    if roll_result >= 0:
        target.d20_send_signal(S_Spell_Mirror_Image_Struck, spell_id, 0)
        target.float_mesfile_line('mes\\combat.mes', 109)
        game.create_history_from_pattern(10, performer, target)

    return True

def getDefenderConcealment(d20a):
    target = d20a.target
    defenderConcealment = tpdp.EventObjAttack()
    defenderConcealment.attack_packet.set_flags(d20a.flags)
    defenderConcealment.attack_packet.target = target
    defenderConcealment.attack_packet.attacker = d20a.performer
    return defenderConcealment.dispatch(target, OBJ_HANDLE_NULL, ET_OnGetDefenderConcealmentMissChance, EK_NONE)

def getAttackerConcealment(performer):
    performerConcealment = tpdp.EventObjObjectBonus()
    performerConcealment.dispatch(performer, ET_OnGetAttackerConcealmentMissChance, EK_NONE)
    return performerConcealment.bonus_list.get_highest()

def getSuppressConcealment(performer, target):
    #suppressingConditions can be easily expanded with new conditions if necessary
    suppressingConditions = [tpdp.get_condition_ref("sp-True Strike"), tpdp.get_condition_ref("Weapon Seeking")]
    if any(performer.d20_query_with_data(Q_Critter_Has_Condition, conRef, 0) for conRef in suppressingConditions):
        return True
    elif performer.can_blindsee(target):
        return True
    elif performer.d20_query("Ignore Concealment"): #Example for Arcane Archer; not implemented in AA
        return True
    return False

def rollConcealment(concealmentMissChance):
    concealmentDice = dice_new("1d100")
    concealmentDiceRoll = concealmentDice.roll()
    if concealmentDiceRoll > concealmentMissChance:
        return True, concealmentDiceRoll
    return False, concealmentDiceRoll

def toHitResult(performerToHit, targetAc):
    toHitDice = dice_new("1d20")
    toHitRoll = toHitDice.roll()
    if toHitRoll == 1:
        return False, toHitRoll
    elif toHitRoll == 20:
        return True, toHitRoll
    elif toHitRoll + performerToHit >= targetAc:
        return True, toHitRoll
    return False, toHitRoll


def to_hit_processing(d20a):
    performer = d20a.performer #auto performer = d20a.d20APerformer;
    d20Data = d20a.data1 #auto d20Data = d20a.data1;
    target = d20a.target #auto tgt = d20a.d20ATarget;
    if not target:
        return

    #Mirror Image
    numberOfMirrorImages = target.d20_query(Q_Critter_Has_Mirror_Image)
    if numberOfMirrorImages:
        if hitMirrorImage(d20a, numberOfMirrorImages):
            return

    #Concealment
    debug_print("Concealment")
    targetConcealment = getDefenderConcealment(d20a)
    performerCanSuppressConcealment = getSuppressConcealment(performer, target)
    if performerCanSuppressConcealment:
        targetConcealment = 0
    concealmentMissChance = max(targetConcealment, getAttackerConcealment(performer))
    if concealmentMissChance > 0:
        is_success, miss_chance_roll = rollConcealment(concealmentMissChance)
        if is_success:
            roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 60, miss_chance_roll, 194, 193)
            d20a.roll_id_1 = roll_id
        else: # concealment miss
            roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 60, miss_chance_roll, 195, 193)
            d20a.roll_id_1 = roll_id

            # Blind fight - give second chance
            if not performer.has_feat(feat_blind_fight):
                return

            is_success, miss_chance_roll = rollConcealment(concealmentMissChance)
            if not is_success:
                roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 61, miss_chance_roll, 195, 193)
                return
            
            roll_id = roll_history.add_percent_chance_roll(performer, target, concealmentMissChance, 61, miss_chance_roll, 194, 193)
            d20a.roll_id_2 = roll_id

    #ToHitBonus Actions
    debug_print("To Hit")
    to_hit_eo = tpdp.EventObjAttack()
    to_hit_eo.attack_packet.set_flags(d20a.flags)
    to_hit_eo.attack_packet.target = target
    to_hit_eo.attack_packet.action_type = d20a.action_type #dispIoToHitBon.attackPacket.d20ActnType = d20a.action_type
    to_hit_eo.attack_packet.attacker = performer
    to_hit_eo.attack_packet.event_key = d20Data #dispIoToHitBon.attackPacket.dispKey = d20Data
    unarmed = OBJ_HANDLE_NULL
    if to_hit_eo.attack_packet.get_flags() & D20CAF_TOUCH_ATTACK:
        to_hit_eo.attack_packet.set_weapon_used(unarmed)
    elif to_hit_eo.attack_packet.get_flags() & D20CAF_SECONDARY_WEAPON:
        offhandItem = performer.item_worn_at(item_wear_weapon_secondary)
        if offhandItem == OBJ_HANDLE_NULL or offhandItem.type != obj_t_weapon:
            to_hit_eo.attack_packet.set_weapon_used(unarmed)
        else:
            to_hit_eo.attack_packet.set_weapon_used(offhandItem)
    else:
        mainhandItem = performer.item_worn_at(item_wear_weapon_primary)
        if mainhandItem == OBJ_HANDLE_NULL or mainhandItem.type != obj_t_weapon:
            to_hit_eo.attack_packet.set_weapon_used(unarmed)
        else:
            to_hit_eo.attack_packet.set_weapon_used(mainhandItem)
    to_hit_eo.attack_packet.ammo_item = performer.get_ammo_used()
    flags = to_hit_eo.attack_packet.get_flags()
    flags |= D20CAF_FINAL_ATTACK_ROLL
    to_hit_eo.attack_packet.set_flags(flags)
    to_hit_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnGetBucklerAcPenalty , EK_NONE)
    to_hit_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnToHitBonus2, EK_NONE) # // note: the "Global" condition has ToHitBonus2 hook that dispatches the ToHitBonusBase
    to_hit_bon_final = to_hit_eo.dispatch(target, OBJ_HANDLE_NULL, ET_OnToHitBonusFromDefenderCondition, EK_NONE)

    #targetAc Actions
    debug_print("Target AC")
    target_ac_eo = to_hit_eo.__copy__()
    target_ac_eo.bonus_list.reset()
    
    target_ac_eo.dispatch(target, OBJ_HANDLE_NULL, ET_OnGetAC, EK_NONE)
    tgt_ac_final = target_ac_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnGetAcModifierFromAttacker, EK_NONE)

    #Check if attacks hits
    attackDidHit, toHitRoll = toHitResult(to_hit_bon_final, tgt_ac_final)

    critAlwaysCheat = cheats.critical #Note: changed behavior from vanilla (this used to toggle the property)

    #Check for special hit conditions
    if not attackDidHit:
        if to_hit_eo.attack_packet.get_flags() & D20CAF_ALWAYS_HIT:
            attackDidHit = True
        elif critAlwaysCheat:
            attackDidHit = True
        else:
            #Reroll Check
            if performer.d20_query(Q_RerollAttack):
                tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags()  )
                rerollDidHit, toHitRoll = toHitResult(to_hit_bon_final, tgt_ac_final)
                flags = to_hit_eo.attack_packet.get_flags()
                flags |= D20CAF_REROLL
                to_hit_eo.attack_packet.set_flags(flags)
                if not rerollDidHit:
                    logbook.inc_misses(performer)
                else:
                    attackDidHit = True

    if not attackDidHit:
        debug_print("Missed")
        roll_id = tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags()  )
        d20a.roll_id_0 = roll_id
        return

    #We have a hit sir!
    debug_print("Scored a hit")
    flags = to_hit_eo.attack_packet.get_flags()
    flags |= D20CAF_HIT
    to_hit_eo.attack_packet.set_flags(flags)
    logbook.inc_hits(performer)
    

    #Check if attack was a critical hit
    performerCritRange = to_hit_eo.__copy__()
    performerCritRange.bonus_list.reset()
    critRange = 21 - performerCritRange.dispatch(performer, OBJ_HANDLE_NULL, ET_OnGetCriticalHitRange, EK_NONE)

    if target.d20_query(Q_Critter_Is_Immune_Critical_Hits):
        isCritical = False
    elif toHitRoll == 20:
        isCritical = True
    elif toHitRoll >= critRange:
        isCritical = True
    elif critAlwaysCheat:
        isCritical = True
    else:
        isCritical = False

    #Check to Confirm Critical Hit
    crit_hit_roll = -1
    if isCritical:
        debug_print("Confirm critical:")
        to_hit_bon_final += to_hit_eo.dispatch(performer, OBJ_HANDLE_NULL, ET_OnConfirmCriticalBonus, EK_NONE)
        critConfirmed, crit_hit_roll = toHitResult(to_hit_bon_final, tgt_ac_final)

        #Check for special confirm conditions
        if not critConfirmed:
            if performer.d20_query("Always Confirm Criticals"):
                critConfirmed = True
            elif critAlwaysCheat:
                critConfirmed = True
            else:
                if performer.d20_query(Q_RerollCritical):
                    tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags(), crit_hit_roll  )
                    critConfirmed, crit_hit_roll = toHitResult(to_hit_bon_final, tgt_ac_final)
                    #no reroll flag seems to be added in original code

        if critConfirmed:
            debug_print("Crit confirm")
            flags = to_hit_eo.attack_packet.get_flags()
            flags |= D20CAF_CRITICAL
            to_hit_eo.attack_packet.set_flags(flags)
    
    #Deflect Arrows
    #Unsure why it is done after confirm crit,
    #If done before, history window for normal attack
    #could be done earlier
    #dispIoToHitBon.Dispatch(dispIoToHitBon.attackPacket.victim, objHndl::null, dispTypeDeflectArrows, DK_NONE)
    #unsure why it is not simply tgt, will copy it
    to_hit_eo.dispatch(to_hit_eo.attack_packet.target, OBJ_HANDLE_NULL, ET_OnDeflectArrows, EK_NONE)

    handle_sanctuary(to_hit_eo, d20a)
    
    #Set flags
    debug_print("Final")
    d20a.flags = to_hit_eo.attack_packet.get_flags()
    roll_id = tpdp.create_history_attack_roll(performer, target, toHitRoll, to_hit_eo.bonus_list, target_ac_eo.bonus_list, to_hit_eo.attack_packet.get_flags(), crit_hit_roll  )
    d20a.roll_id_0 = roll_id
    return

# Action lost due to Sanctuary # Work out current Dex bonus to AC #Performer to Hit Bonus #Check if real target was hit #A roll of 1 indicates hit on real target #Get spell_id and spellName #suppressingConditions can be easily expanded with new conditions if necessary #Example for Arcane Archer; not implemented in AA #auto performer = d20a.d20APerformer; #auto d20Data = d20a.data1; #auto tgt = d20a.d20ATarget; #Mirror Image #Concealment # concealment miss # Blind fight - give second chance #ToHitBonus Actions #dispIoToHitBon.attackPacket.d20ActnType = d20a.action_type #dispIoToHitBon.attackPacket.dispKey = d20Data # // note: the "Global" condition has ToHitBonus2 hook that dispatches the ToHitBonusBase #targetAc Actions #Check if attacks hits #Note: changed behavior from vanilla (this used to toggle the property) #Check for special hit conditions #Reroll Check #We have a hit sir! #Check if attack was a critical hit #Check to Confirm Critical Hit #Check for special confirm conditions #no reroll flag seems to be added in original code #Deflect Arrows #Unsure why it is done after confirm crit, #If done before, history window for normal attack #could be done earlier #dispIoToHitBon.Dispatch(dispIoToHitBon.attackPacket.victim, objHndl::null, dispTypeDeflectArrows, DK_NONE) #unsure why it is not simply tgt, will copy it #Set flags