Example #1
0
class DragonForm(Action):
    def __init__(self, name, conf, adv):
        self.name = name
        self.conf = conf
        self.adv = adv
        self.cancel_by = []
        self.interrupt_by = []
        self.disabled = False
        self.shift_event = Event('dragon')
        self.act_event = Event('dact')
        self.end_event = Event('dragon_end')
        self.delayed = set()

        self.ds_reset()
        self.act_list = []
        self.act_sum = []
        self.repeat_act = False

        self.dx_list = [dx for dx, _ in self.conf.find(r'^dx\d+$')]

        self.ds_event = Event('ds')
        self.ds_event.name = 'ds'
        self.dx_event = Event('dx')

        self.action_timer = None

        self.shift_start_time = 0
        self.shift_end_timer = Timer(self.d_shift_end)
        self.idle_event = Event('idle')

        self.c_act_name = None
        self.c_act_conf = None
        self.dracolith_mod = self.adv.Modifier('dracolith', 'att', 'dragon', 0)
        self.dracolith_mod.get = self.ddamage
        self.dracolith_mod.off()
        self.shift_mods = [self.dracolith_mod]
        self.shift_spd_mod = None

        self.off_ele_mod = None
        if self.adv.slots.c.ele != self.adv.slots.d.ele:
            self.off_ele_mod = self.adv.Modifier('off_ele', 'att', 'dragon', -1/3)
            self.off_ele_mod.off()

        self.dragon_gauge = 0
        self.dragon_gauge_val = self.conf.gauge_val
        self.conf.gauge_iv = min(int(self.adv.duration/12), 15)
        self.dragon_gauge_timer = Timer(self.auto_gauge, timeout=max(1, self.conf.gauge_iv), repeat=1).on()
        self.dragon_gauge_pause_timer = None
        self.dragon_gauge_timer_diff = 0
        self.max_gauge = 1000
        self.shift_cost = 500

        self.shift_count = 0
        self.shift_silence = False

        self.is_dragondrive = False
        self.can_end = True

        self.allow_end_cd = self.conf.allow_end + (self.conf.ds.startup + self.conf.ds.startup) / self.speed()
        self.allow_force_end_timer = Timer(self.set_allow_end, timeout=self.allow_end_cd)
        self.allow_end = False

    def reset_allow_end(self):
        if self.is_dragondrive:
            self.allow_end = True
        else:
            log('allow_end', self.allow_end_cd)
            self.allow_end = False
            self.allow_force_end_timer = Timer(self.set_allow_end, timeout=self.allow_end_cd)
            self.allow_force_end_timer.on()
            self.allow_end_cd += self.conf.allow_end_step

    def set_allow_end(self, _):
        self.allow_end = True

    def set_dragondrive(self, dd_buff, max_gauge=3000, shift_cost=1200, drain=150):
        self.disabled = False
        self.is_dragondrive = True
        self.shift_event = Event('dragondrive')
        self.dragondrive_end_event = Event('dragondrive_end')
        ratio = max_gauge / self.max_gauge
        self.dragon_gauge *= ratio
        self.dragon_gauge_val *= ratio
        self.max_gauge = max_gauge
        self.shift_cost = shift_cost # does not deduct, but need to have this much pt to shift
        self.drain = drain
        self.dragondrive_buff = dd_buff
        self.dragondrive_timer = Timer(self.d_dragondrive_end)
        return self.dragondrive_buff

    def set_dragonbattle(self, duration):
        self.disabled = False
        self.dragon_gauge = self.max_gauge
        self.conf.duration = duration
        self.can_end = False
        self.repeat_act = True
        if self.conf.ds['sp_db']:
            self.skill_sp = self.conf.ds['sp_db']
        else:
            self.skill_sp = self.conf.ds.sp+15
        self.skill_spc = self.skill_sp
        self.skill_use = -1

    def end_silence(self, t):
        self.shift_silence = False

    def dodge_cancel(self):
        if len(self.dx_list) <= 0:
            return False
        combo = self.conf[self.dx_list[-1]].recovery / self.speed()
        dodge = self.conf.dodge.startup + self.conf.dodge.recovery
        return combo > dodge

    def auto_gauge(self, t):
        self.charge_gauge(self.dragon_gauge_val)

    def pause_auto_gauge(self):
        if self.dragon_gauge_pause_timer is None:
            self.dragon_gauge_timer_diff = self.dragon_gauge_timer.timing - now()
        else:
            self.dragon_gauge_timer_diff = self.dragon_gauge_pause_timer.timing - now()
        self.dragon_gauge_timer.off()

    def resume_auto_gauge(self, t):
        self.dragon_gauge_pause_timer = None
        self.auto_gauge(t)
        self.dragon_gauge_timer.on()

    def add_drive_gauge_time(self, delta, skill_pause=False):
        max_duration = self.max_gauge/self.drain
        duration = self.dragondrive_timer.timing - now()
        max_add = max_duration - duration
        if skill_pause:
            add_time = min(delta, max_add)
        else:
            add_time = min(delta/self.drain, max_add)
        duration = self.dragondrive_timer.add(add_time)
        if duration <= 0:
            self.d_dragondrive_end('<gauge deplete>')
        else:
            self.dragon_gauge = (duration/max_duration)*self.max_gauge
            if add_time != 0:
                log('drive_time' if not skill_pause else 'skill_pause', f'{add_time:+2.4}', f'{duration:2.4}', f'{int(self.dragon_gauge)}/{int(self.max_gauge)}')

    def charge_gauge(self, value, utp=False, dhaste=True):
        # if dhaste is None:
        #     dhaste = not utp
        dh = self.adv.mod('dh') if dhaste else 1
        value = self.adv.sp_convert(dh, value)
        delta = min(self.dragon_gauge+value, self.max_gauge) - self.dragon_gauge
        if self.is_dragondrive and self.dragondrive_buff.get():
            self.add_drive_gauge_time(delta)
        elif delta != 0:
            self.dragon_gauge += delta
            if utp:
                log('dragon_gauge', '{:+} utp'.format(int(delta)), f'{int(self.dragon_gauge)}/{int(self.max_gauge)}', value)
            else:
                log('dragon_gauge', '{:+.2f}%'.format(delta/self.max_gauge*100), '{:.2f}%'.format(self.dragon_gauge/self.max_gauge*100))

    @allow_acl
    def dtime(self):
        return self.conf.dshift.startup + self.conf.duration * self.adv.mod('dt') + self.conf.exhilaration * (self.off_ele_mod is None)

    @allow_acl
    def ddamage(self):
        return self.conf.dracolith + self.adv.mod('da') - 1

    def ds_check(self):
        return self.skill_use != 0 and self.skill_spc >= self.skill_sp

    def ds_charge(self, value):
        if self.skill_use != 0 and self.skill_spc < self.skill_sp:
            self.skill_spc += self.adv.sp_convert(self.adv.sp_mod('x'), value)
            if self.skill_spc > self.skill_sp:
                self.skill_spc = self.skill_sp
            log(self.c_act_name, 'sp', f'{self.skill_spc}/{self.skill_sp}')

    def ds_reset(self):
        self.skill_use = self.conf.ds.uses
        self.skill_sp = self.conf.ds.sp
        self.skill_spc = self.skill_sp

    def d_shift_end(self, t):
        if self.action_timer is not None:
            self.action_timer.off()
            self.action_timer = None
        duration = now()-self.shift_start_time
        shift_dmg = g_logs.shift_dmg
        g_logs.log_shift_dmg(False)
        count = self.clear_delayed()
        if count > 0:
            log('cancel', self.c_act_name, f'by shift end', f'lost {count} hit{"s" if count > 1 else ""}')
        log(self.name, '{:.2f}dmg / {:.2f}s, {:.2f} dps'.format(shift_dmg, duration, shift_dmg/duration), ' '.join(self.act_sum))
        self.act_sum = []
        self.act_list = []
        if self.off_ele_mod is not None:
            self.off_ele_mod.off()
        if self.shift_spd_mod is not None:
            self.shift_spd_mod.off()
        self.ds_reset()
        if not self.is_dragondrive:
            self.shift_silence = True
            Timer(self.end_silence).on(10)
            self.dragon_gauge_pause_timer = Timer(self.resume_auto_gauge).on(self.dragon_gauge_timer_diff)
        self.status = Action.OFF
        self._setprev() # turn self from doing to prev
        self._static.doing = self.nop
        self.end_event()
        self.idle_event()

    def d_dragondrive_end(self, t):
        self.dragon_gauge = 0
        log('dragondrive', 'end', t if isinstance(t, str) else '<timeout>')
        self.dragondrive_buff.off()
        self.shift_silence = True
        Timer(self.end_silence).on(10)
        self.status = Action.OFF
        self._setprev() # turn self from doing to prev
        self._static.doing = self.nop
        self.dragondrive_end_event()
        self.idle_event()

    def act_timer(self, act, time, next_action=None):
        if self.c_act_name == 'dodge':
            self.action_timer = Timer(act, time)
        else:
            self.action_timer = Timer(act, time / self.speed())
        self.action_timer.next_action = next_action
        return self.action_timer.on()

    def d_act_start_t(self, t):
        self.action_timer = None
        self.d_act_start(t.next_action)

    def d_act_start(self, name):
        if name in self.conf and self._static.doing == self and self.action_timer is None:
            self.prev_act = self.c_act_name
            self.prev_conf = self.c_act_conf
            self.c_act_name = name
            self.c_act_conf = self.conf[name]
            self.act_timer(self.d_act_do, self.c_act_conf.startup)

    def d_act_do(self, t):
        if self.c_act_name == 'end':
            self.d_shift_end(None)
            self.shift_end_timer.off()
            return
        
        actconf = self.conf[self.c_act_name]
        e = self.act_event
        e.name = self.c_act_name 
        e.base = self.c_act_name
        e.group = 'dragon'
        self.adv.actmod_on(e)
        
        try:
            getattr(self.adv, f'{self.c_act_name}_before')(e)
        except AttributeError:
            pass

        final_mt = self.adv.schedule_hits(e, self.conf[self.c_act_name])
        if final_mt:
            final_mt.actmod = True
            final_mt.actmod = True
            try:
                final_mt.proc = getattr(self.adv, f'{self.c_act_name}_proc')
            except AttributeError:
                pass
        else:
            self.adv.actmod_off(e)
            try:
                getattr(self.adv, f'{self.c_act_name}_proc')(e)
            except AttributeError:
                pass
        if self.c_act_name == 'ds':
            self.skill_use -= 1
            self.skill_spc = 0
            self.act_sum.append('s')
            self.ds_event()
            self.shift_end_timer.add(actconf.startup+actconf.recovery)
        elif self.c_act_name.startswith('dx'):
            if len(self.act_sum) > 0 and self.act_sum[-1][0] == 'c' and int(self.act_sum[-1][1]) < int(self.c_act_name[-1]):
                self.act_sum[-1] = 'c'+self.c_act_name[-1]
            else:
                self.act_sum.append('c'+self.c_act_name[-1])
            self.dx_event.index = int(self.c_act_name[-1])
            self.dx_event()

        self.d_act_next()

    def d_act_next(self):
        nact = None
        if self.repeat_act and not self.act_list:
            self.parse_act(self.conf.act)
        if self.act_list:
            if self.act_list[0] != 'ds' or self.ds_check():
                if self.act_list[0] == 'end' and not self.allow_end:
                    nact = None
                else:
                    nact = self.act_list.pop(0)
            # print('CHOSE BY LIST', nact, self.c_act_name)
        if nact is None:
            if self.c_act_name[0:2] == 'dx':
                nact = 'dx{}'.format(int(self.c_act_name[2])+1)
                if not nact in self.dx_list:
                    if self.ds_check():
                        nact = 'ds'
                    elif self.dodge_cancel():
                        nact = 'dodge'
                    else:
                        nact = 'dx1'
            else:
                nact = 'dx1'
            # print('CHOSE BY DEFAULT', nact, self.c_act_name)
        if nact == 'ds' or nact == 'dodge' or (nact == 'end' and self.c_act_name != 'ds'): # cancel
            count = self.clear_delayed()
            if count > 0:
                log('cancel', self.c_act_name, f'by {nact}', f'lost {count} hit{"s" if count > 1 else ""}')
            self.act_timer(self.d_act_start_t, self.conf.latency, nact)
        else: # regular recovery
            self.act_timer(self.d_act_start_t, self.c_act_conf.recovery, nact)

    def parse_act(self, act_str):
        if self.status != Action.OFF and not self.repeat_act:
            return
        act_str = act_str.strip()
        self.act_list = []
        skill_usage = 0

        for a in act_str.split('-'):
            if a[0] == 'c' or a[0] == 'x':
                for i in range(1, int(a[1])+1):
                    dxseq = 'dx{}'.format(i)
                    if dxseq in self.dx_list:
                        self.act_list.append(dxseq)
                try:
                    if self.dodge_cancel() or self.act_list[-1] != self.dx_list[-1]:
                        self.act_list.append('dodge')
                except IndexError:
                    pass
            else:
                try:
                    if len(self.act_list) > 0 and self.act_list[-1] == 'dodge':
                        self.act_list.pop()
                except IndexError:
                    pass
                if (a == 's' or a == 'ds') and (self.skill_use <= -1 or skill_usage < self.skill_use):
                    self.act_list.append('ds')
                    skill_usage += 1
                elif a == 'end' and self.can_end:
                    self.act_list.append('end')
                elif a == 'dodge':
                    self.act_list.append('dodge')

    def act(self, act_str):
        self.parse_act(act_str)
        return self()

    @allow_acl
    def check(self, dryrun=True):
        if self.disabled or self.shift_silence:
            return False
        if self.dragon_gauge < self.shift_cost and not (self.is_dragondrive and self.dragondrive_buff.get()):
            return False
        doing = self.getdoing()
        if not doing.idle:
            if isinstance(doing, S) or isinstance(doing, DragonForm):
                return False
            if dryrun == False:
                if doing.status == Action.STARTUP:
                    doing.startup_timer.off()
                    log('interrupt', doing.name , 'by '+self.name, 'after {:.2f}s'.format(now()-doing.startup_start))
                elif doing.status == Action.RECOVERY:
                    doing.recovery_timer.off()
                    log('cancel', doing.name , 'by '+self.name, 'after {:.2f}s'.format(now()-doing.recover_start))
        return True

    def __call__(self):
        if not self.check(dryrun=False):
            return False
        if self.is_dragondrive:
            self.act_list = ['end']
            if self.dragondrive_buff.get():
                self.d_dragondrive_end('<turn off>')
                return True
            else:
                log('cast', 'dragondrive', self.name, f'base duration {self.dragon_gauge/self.drain:.4}s')
                self.dragondrive_timer.on(self.dragon_gauge/self.drain)
                self.dragondrive_buff.on()
        else:
            log('cast', 'dshift', self.name)
            if len(self.act_list) == 0:
                self.parse_act(self.conf.act)
            self.dragon_gauge -= self.shift_cost
            if self.off_ele_mod is not None:
                self.off_ele_mod.on()
            if self.shift_spd_mod is not None:
                self.shift_spd_mod.on()
            self.pause_auto_gauge()
        self.shift_count += 1
        self.status = Action.STARTUP
        self._setdoing()
        g_logs.log_shift_dmg(True)
        self.shift_start_time = now()
        self.shift_end_timer.on(self.dtime())
        self.reset_allow_end()
        self.shift_event()
        self.d_act_start('dshift')
        return True
Example #2
0
class Buff(object):
    MAXHP_CAP = 1.30
    _static = Static({
        'all_buffs': [],
        'adv': None
    })

    def __init__(self, name='<buff_noname>', value=0, duration=0, mtype='att', morder=None, modifier=None, hidden=False):
        self.name = name
        self.__value = value
        self.duration = duration
        self.mod_type = mtype
        self.mod_order = morder or ('chance' if self.mod_type == 'crit' else 'buff')
        self.bufftype = 'misc' if hidden else 'self'

        self.bufftime = self._bufftime if self.duration > 0 else self._no_bufftime
        self.buff_end_timer = Timer(self.buff_end_proc)
        if modifier:
            self.modifier = modifier
            self.get = self.modifier.get
        elif mtype != 'effect':
            self.modifier = Modifier('mod_' + self.name, self.mod_type, self.mod_order, 0)
            self.modifier.get = self.get
        else:
            self.modifier = None
        self.dmg_test_event = Event('dmg_formula')
        self.dmg_test_event.dmg_coef = 1
        self.dmg_test_event.dname = 'test'

        self.hidden = hidden

        self.__stored = 0
        self.__active = 0
        # self.on()

    def logwrapper(self, *args):
        if not self.hidden:
            log('buff', *args)

    def _no_bufftime(self):
        return 1

    def _ex_bufftime(self):
        return 1 + self._static.adv.sub_mod('buff', 'ex')

    def _bufftime(self):
        return self._static.adv.mod('buff', operator=operator.add)

    def _debufftime(self):
        return self._static.adv.mod('debuff', operator=operator.add)

    def no_bufftime(self):
        self.bufftime = self._no_bufftime
        return self

    def ex_bufftime(self):
        self.bufftime = self._ex_bufftime
        return self

    def value(self, newvalue=None):
        if newvalue:
            self.logwrapper(self.name, f'{self.mod_type}({self.mod_order}): {newvalue:.02f}', 'buff value change')
            return self.set(newvalue)
        else:
            return self.get()

    def get(self):
        if self.__active:
            return self.__value
        else:
            return 0

    def set(self, v, d=None):
        self.__value = v
        if d != None:
            self.duration = d
        return self
        

    def stack(self):
        stack = 0
        for i in self._static.all_buffs:
            if i.name == self.name:
                if i.__active != 0:
                    stack += 1
        return stack

    def valuestack(self):
        stack = 0
        value = 0
        for i in self._static.all_buffs:
            if i.name == self.name:
                if i.__active != 0:
                    stack += 1
                    value += i.__value
        return value, stack

    def effect_on(self):
        return self.modifier and self.modifier.on()

    def effect_off(self):
        return self.modifier and self.modifier.off()

    def buff_end_proc(self, e):
        self.logwrapper(self.name, f'{self.mod_type}({self.mod_order}): {self.value():.02f}', 'buff end <timeout>')
        self.__active = 0

        if self.__stored:
            idx = len(self._static.all_buffs)
            while 1:
                idx -= 1
                if idx < 0:
                    break
                if self == self._static.all_buffs[idx]:
                    self._static.all_buffs.pop(idx)
                    break
            self.__stored = 0
        value, stack = self.valuestack()
        if stack > 0:
            self.logwrapper(self.name, f'{self.mod_type}({self.mod_order}): {value:.02f}', f'buff stack <{stack}>')
        self.effect_off()

    def count_team_buff(self):
        base_mods = [
            Modifier('base_cc', 'crit', 'chance', 0.12),
            Modifier('base_killer', 'killer','passive', 0.30)
        ]
        self.dmg_test_event.modifiers = ModifierDict()
        for mod in base_mods:
            self.dmg_test_event.modifiers.append(mod)
        for b in filter(lambda b: b.get() and b.bufftype == 'simulated_def', self._static.all_buffs):
            self.dmg_test_event.modifiers.append(b.modifier)

        self.dmg_test_event()
        no_team_buff_dmg = self.dmg_test_event.dmg

        placeholders = []
        for b in filter(lambda b: b.get() and b.bufftype in ('team', 'debuff'), self._static.all_buffs):
            placehold = None
            if b.modifier.mod_type == 's':
                placehold = Modifier('placehold_sd', 'att', 'sd', b.modifier.get() / 2)
            elif b.modifier.mod_type == 'spd':
                placehold = Modifier('placehold_spd', 'att', 'spd', b.modifier.get())
            elif b.modifier.mod_type.endswith('_killer'):
                placehold = Modifier('placehold_k', 'killer', 'passive', b.modifier.get())
            if placehold:
                self.dmg_test_event.modifiers.append(placehold)
                placeholders.append(placehold)
            else:
                self.dmg_test_event.modifiers.append(b.modifier)

        self.dmg_test_event()
        team_buff_dmg = self.dmg_test_event.dmg
        log('buff', 'team', team_buff_dmg / no_team_buff_dmg - 1)

        for mod in chain(base_mods, placeholders):
            mod.off()


    def on(self, duration=None):
        if self.mod_type == 'maxhp':
            max_hp = self._static.adv.mod('maxhp')
            if max_hp >= Buff.MAXHP_CAP:
                return self
            value = self.__value
            mod_val = min(value, max(Buff.MAXHP_CAP-max_hp, 0))
            self._static.adv.set_hp((self._static.adv.hp*max_hp+value*100)/(max_hp+mod_val))

        d = (duration or self.duration) * self.bufftime()
        if self.__active == 0:
            self.__active = 1
            if self.__stored == 0:
                self._static.all_buffs.append(self)
                self.__stored = 1
            if d >= 0:
                self.buff_end_timer.on(d)
            proc_type = 'start'
        else:
            if d >= 0:
                self.buff_end_timer.on(d)
            proc_type = 'refresh'
            
        self.logwrapper(self.name, f'{self.mod_type}({self.mod_order}): {self.value():.02f}', f'buff {proc_type} <{d:.02f}s>')
        value, stack = self.valuestack()
        if stack > 1:
            log('buff', self.name, f'{self.mod_type}({self.mod_order}): {value:.02f}', f'buff stack <{stack}>')

        if self.mod_type == 'defense':
            Event('defchain').on()
            if self.bufftype == 'team':
                log('buff', 'team_defense', 'proc team doublebuffs')

        if self.mod_type == 'regen':
            # may need to make this part global since game always regen all stacks at same ticks
            self.set_hp_event = Event('set_hp')
            self.set_hp_event.delta = self.get()
            self.regen_timer = Timer(self.hp_regen, 3.9, True)
        else:
            self.effect_on()
        return self

    def hp_regen(self, t):
        self.set_hp_event()

    def off(self):
        if self.__active == 0:
            return
        self.logwrapper(self.name, f'{self.mod_type}({self.mod_order}): {self.value():.02f}', f'buff end <turn off>')
        self.__active = 0
        self.effect_off()
        self.buff_end_timer.off()
        return self

    def timeleft(self):
        return -1 if self.duration == -1 else (self.buff_end_timer.timing-now())

    def add_time(self, delta):
        self.buff_end_timer.add(delta)
Example #3
0
class DragonForm:
    def __init__(self, name, conf, adv, dragon, dform_mode=-1, unique_dform=False):
        self.name = name
        self.conf = conf
        self.adv = adv
        self.dragon = dragon
        self.dform_mode = dform_mode
        self.unique_dform = unique_dform

        self.shift_start_proc = None
        self.shift_end_proc = None
        self.config_actions()
        # events
        self.status = False
        self.disabled_reasons = set()
        self.dp_event = Event("dp")
        self.shift_end_timer = Timer(self.d_shift_end)
        self.shift_silence_timer = Timer(None, 10)
        self.can_end = True
        self.l_shift = Listener("dshift", self.d_shift_start, order=2)
        self.l_s = Listener("s", self.l_ds_pause, order=0)
        self.l_s_end = Listener("s_end", self.l_ds_resume, order=0)
        self.l_s_final_end = Listener("s_end", self.d_shift_end, order=0)
        self.l_s.off()
        self.l_s_end.off()
        self.l_s_final_end.off()
        self.shift_skills = ("ds1", "ds2")

        self.allow_end_cd = self.conf.allow_end + self.dstime()
        self.shift_end_reason = None

        # mods
        self.dracolith_mod = self.adv.Modifier("dracolith", "ex", "dragon", 0)
        self.dracolith_mod.get = self.ddamage
        self.dracolith_mod.off()
        self.shift_mods = [self.dracolith_mod]
        self.shift_spd_mod = None
        self.off_ele = self.adv.slots.c.ele != self.conf.d.ele
        self.previous_x = DEFAULT

        # gauge
        self.auto_gauge_val = 0.1  # percent
        self.auto_gauge_iv = min(int(self.adv.duration / 12), 15)
        self.dragon_gauge_timer = Timer(self.auto_gauge, timeout=max(1, self.auto_gauge_iv), repeat=1).on()
        self.dragon_gauge_pause_timer = None
        self.dragon_gauge_timer_diff = 0

        self.dragon_gauge = 0
        self.max_dragon_gauge = 1000
        self.shift_cost = 500
        self.log_utp = False

        # dragonbattle
        self.is_dragonbattle = False

    def set_dragonbattle(self):
        self.is_dragonbattle = True
        if self.dform_mode == 0:  # ddrive chara cant do dragon battle
            self.adv.stop()
            return
        for skey in self.shift_skills:
            self.adv.a_s_dict[skey].dragonbattle_skill = True
        self.dragon_gauge += self.shift_cost
        g_logs.log_dact_as_act = True

    def config_actions(self):
        # merge confs into adv conf
        self.dx_max = 0
        for dx, dxconf in self.conf.find(r"^dx\d+$"):
            self.adv.conf[dx] = dxconf
            self.dx_max = max(self.dx_max, int(dx[2:]))
        self.default_x_loop = self.conf["default_x_loop"] or self.dx_max  # the default combo idx to end combo on
        for fs, fsconf in self.conf.find(r"^dfs\d*(_[A-Za-z0-9]+)?$"):
            self.adv.conf[fs] = fsconf
            if not self.unique_dform:
                for sfn in ("before", "proc"):
                    self.adv.rebind_function(self.dragon, f"{fs}_{sfn}", f"{fs}_{sfn}", overwrite=False)
        self.ds_final = None
        for ds, dsconf in self.conf.find(r"^ds\d*(_[A-Za-z0-9]+)?$"):
            self.adv.conf[ds] = dsconf
            ds_base = ds.split("_")[0]
            if not self.unique_dform:
                for sfn in ("before", "proc"):
                    self.adv.rebind_function(self.dragon, f"{ds_base}_{sfn}", f"{ds_base}_{sfn}", overwrite=False)
            if ds.startswith("ds99") or dsconf.get("final"):
                self.ds_final = ds.split("_")[0]
        # make separate dodge action, may want to handle forward/backward later
        self.d_dodge = Dodge("dodge", self.conf.dodge)
        self.d_shift = Shift("dshift", self.name, self.conf.dshift)
        self.d_end = Shift("dend", self.name, self.conf.dend)

        self.shift_event = Event("dragon")
        self.end_event = Event("dragon_end")

        if abs(self.dform_mode) == 1:
            try:
                self.shift_start_proc = self.dragon.shift_start_proc
            except AttributeError:
                pass
            try:
                self.shift_end_proc = self.dragon.shift_end_proc
            except AttributeError:
                pass

    def in_dform(self):
        return self.status

    def in_ddrive(self):
        return False

    @property
    def shift_silence(self):
        return bool(self.shift_silence_timer.online)

    def set_disabled(self, reason):
        if self.disabled_reasons is not None:
            self.disabled_reasons.add(reason)

    def unset_disabled(self, reason):
        if self.disabled_reasons is not None:
            self.disabled_reasons.discard(reason)

    @property
    def disabled(self):
        return bool(self.disabled_reasons)

    def set_dacts_enabled(self, enabled):
        for xact in self.adv.a_x_dict[DRG].values():
            xact.enabled = enabled
        fsact = self.adv.a_fs_dict.get("dfs")
        if fsact:
            fsact.set_enabled(enabled)
        for skey in self.shift_skills:
            self.adv.a_s_dict[skey].set_enabled(enabled)

    def auto_dodge(self, index=None):
        index = index or self.dx_max
        d_combo = self.adv.a_x_dict[DRG][index]
        if "dodge" not in d_combo.conf.cancel:
            return False
        dodge_t = d_combo.conf.cancel["dodge"] + self.d_dodge.getstartup() + self.d_dodge.getrecovery()
        if d_combo.conf.cancel["dx1"]:
            combo_t = d_combo.conf.cancel["dx1"] / d_combo.speed()
        else:
            combo_t = d_combo.getrecovery()
        return dodge_t < combo_t

    def dx_dodge_or_wait(self, index):
        # log("dx_dodge_or_skill", "x{}: c on {}, s on {}".format(index, self.default_ds_x, self.default_x_loop), self.adv.ds1.check())
        if index == self.default_x_loop:
            return self.d_dodge if self.auto_dodge(index=index) else False
        return False

    def pause_auto_gauge(self):
        if self.is_dragonbattle:
            return
        if self.dragon_gauge_pause_timer is None:
            self.dragon_gauge_timer_diff = self.dragon_gauge_timer.timing - now()
        else:
            self.dragon_gauge_timer_diff = self.dragon_gauge_pause_timer.timing - now()
        self.dragon_gauge_timer.off()

    def resume_auto_gauge(self, t):
        self.dragon_gauge_pause_timer = None
        self.auto_gauge(t)
        self.dragon_gauge_timer.on()

    def l_ds_pause(self, e):
        if self.status and e.base in self.shift_skills:
            self.shift_end_timer.pause()
            log("shift_end_timer", "pause", self.shift_end_timer.timing, self.shift_end_timer.pause_time)

    def l_ds_resume(self, e):
        if self.status and e.act.base in self.shift_skills:
            self.shift_end_timer.resume()
            log("shift_end_timer", "resume", self.shift_end_timer.timing, self.shift_end_timer.timing - now())

    @allow_acl
    def dtime(self):
        return self.conf.duration * self.adv.mod("dt") + self.conf.exhilaration * int(not self.off_ele)

    def dstime(self):
        try:
            return (self.conf.ds.startup + self.conf.ds.recovery) / self.d_shift.speed()
        except TypeError:
            return 0

    def reset_allow_end(self):
        self.allow_force_end_timer = Timer(None, timeout=self.allow_end_cd)
        self.allow_force_end_timer.on()
        self.allow_end_cd = min(self.allow_end_cd + self.conf.allow_end_step, self.dtime())

    @property
    def allow_end(self):
        return not bool(self.allow_force_end_timer.online)

    def dhaste(self):
        return self.adv.mod("dh", operator=operator.add)

    def _charge_dp(self, name, value):
        gauge_before = self.dragon_gauge
        self.dragon_gauge = max(0, min(self.max_dragon_gauge, self.dragon_gauge + value))
        delta = self.dragon_gauge - gauge_before
        if delta != 0 and not self.log_utp:
            log(
                "dgauge",
                name,
                f"{int(delta):+}",
                f"{int(self.dragon_gauge)}/{int(self.max_dragon_gauge)} [{self.dragon_gauge//self.shift_cost}]",
            )
        self.dp_event.name = name
        self.dp_event.value = value
        self.dp_event.delta = delta
        self.dp_event.gauge = self.dragon_gauge
        self.dp_event()
        return value

    def charge_dprep(self, value):
        return self._charge_dp("dprep", float_ceil(self.max_dragon_gauge, value / 100))

    def charge_dp(self, name, value):
        return self._charge_dp(name, float_ceil(value, self.dhaste()))

    def auto_gauge(self, t):
        self._charge_dp("auto", float_ceil(self.max_dragon_gauge * self.auto_gauge_val, self.dhaste()))

    @allow_acl
    def ddamage(self):
        return self.conf.dracolith + self.adv.mod("da", operator=operator.add, initial=0)

    def extend_shift_time(self, value, percent=True):
        max_d = self.dtime() - self.conf.dshift.startup
        cur_d = self.shift_end_timer.timeleft()
        delta_t = value
        if percent:
            delta_t *= max_d
        delta_t = min(max_d, cur_d + delta_t) - cur_d
        if cur_d + delta_t > 0:
            self.shift_end_timer.add(delta_t)
            log("shift_time", f"{delta_t:+2.4}", f"{cur_d+delta_t:2.4}")
        elif self.can_end:
            self.d_shift_end()
            self.shift_end_timer.off()

    def d_shift_start(self, _=None):
        self.status = True
        self.dragon_gauge -= self.shift_cost
        g_logs.set_log_shift(shift_name=self.name)
        if self.shift_start_proc:
            self.shift_start_proc()
        if self.off_ele:
            self.adv.element = self.adv.slots.d.ele
        if self.shift_spd_mod is not None:
            self.shift_spd_mod.on()
        self.pause_auto_gauge()
        for s in self.adv.dskills:
            s.reset_uses()
        self.adv.set_dacl(True)
        self.l_s.on()
        self.l_s_end.on()
        self.set_dacts_enabled(True)
        self.adv.charge_p("dshift", 1.0, dragon_sp=True)
        log("shift_end_timer", "on", self.dtime())
        self.shift_end_timer.on(self.dtime())
        self.reset_allow_end()
        self.shift_event()

    def d_shift_end(self, e=None, reason="timeout"):
        if not self.status or self.is_dragonbattle:
            return False
        doing = self.d_shift.getdoing()
        if self.ds_final and not (isinstance(doing, S) and doing.base in self.shift_skills) and not self.l_s_final_end.get():
            ds_final = self.adv.a_s_dict[self.ds_final]
            if ds_final.ac.conf.final and ds_final.ac.enabled:
                self.shift_end_reason = reason
                self.l_s_final_end.on()
                ds_final.reset_uses()
                ds_final.charged = ds_final.sp
                ds_final()
                self.set_dacts_enabled(False)
                return False

        if self.off_ele:
            self.adv.element = self.adv.slots.c.ele
        if self.shift_spd_mod is not None:
            self.shift_spd_mod.off()
        self.shift_silence_timer.on()
        if self.dragon_gauge_timer_diff > 0:
            self.dragon_gauge_pause_timer = Timer(self.resume_auto_gauge).on(self.dragon_gauge_timer_diff)
        self.l_s.off()
        self.l_s_end.off()
        self.l_s_final_end.off()
        self.set_dacts_enabled(False)
        self.adv.set_dacl(False)
        self.end_event()
        self.d_end()
        self.status = False
        g_logs.set_log_shift(end_reason=self.shift_end_reason or reason)
        self.shift_end_reason = None
        if self.shift_end_proc:
            self.shift_end_proc()
        return True

    def d_shift_partial_end(self):
        if self.status and abs(self.dform_mode) == 1:
            g_logs.set_log_shift(end_reason="partial")

    @allow_acl
    def check(self):
        if self.disabled_reasons or self.shift_silence:
            return False
        if self.dragon_gauge < self.shift_cost:
            return False
        if self.status:
            return False
        doing = self.d_shift.getdoing()
        if not doing.idle:
            if isinstance(doing, S):
                return False
        return True

    def shift(self):
        if not self.check():
            return False
        return self.d_shift()

    def sack(self):
        if self.status and not self.l_s_final_end.get() and self.allow_end:
            self.d_shift_end(reason="forced")
            self.shift_end_timer.off()
            return True
        return False
Example #4
0
class DragonForm(Action):
    def __init__(self, name, conf, adv, ds_proc):
        self.name = name
        self.conf = conf
        self.adv = adv
        self.cancel_by = []
        self.interrupt_by = []
        self.disabled = False
        self.shift_event = Event('dragon')
        self.end_event = Event('dragon_end')

        self.ds_proc = ds_proc
        self.skill_use = self.conf.skill_use
        self.act_list = []
        self.act_sum = []

        self.dx_list = [
            'dx{}'.format(i) for i in range(1, 6)
            if 'dmg' in self.conf['dx{}'.format(i)]
        ]

        self.ds_event = Event('s')
        self.ds_event.name = 'ds'

        self.action_timer = None

        self.shift_start_time = 0
        self.shift_damage_sum = 0
        self.shift_end_timer = Timer(self.d_shift_end)
        self.idle_event = Event('idle')

        self.c_act_name = None
        self.c_act_conf = None
        self.dracolith_mod = self.adv.Modifier('dracolith', 'att', 'dragon', 0)
        self.dracolith_mod.off()
        self.off_ele_mod = None
        if self.adv.slots.c.ele != self.adv.slots.d.ele:
            self.off_ele_mod = self.adv.Modifier('off_ele', 'att', 'dragon',
                                                 -1 / 3)
            self.off_ele_mod.off()

        self.dragon_gauge = 0
        self.dragon_gauge_timer = Timer(self.auto_gauge, repeat=1).on(
            max(1, self.conf.gauge_iv))
        self.max_gauge = 1000
        self.shift_cost = 500

        self.shift_count = 0
        self.shift_silence = False

        self.is_dragondrive = False

    def set_dragondrive(self, dd_buff):
        self.is_dragondrive = True
        self.shift_event = Event('dragondrive')
        self.dragondrive_end_event = Event('dragondrive_end')
        self.max_gauge = 3000
        self.shift_cost = 1200  # does not deduct, but need to have this much pt to shift
        self.dragondrive_buff = dd_buff
        self.dragondrive_timer = Timer(self.d_dragondrive_end)

    def end_silence(self, t):
        self.shift_silence = False

    def dodge_cancel(self):
        if len(self.dx_list) <= 0:
            return False
        combo = self.conf[self.dx_list[-1]].recovery / self.speed()
        dodge = self.conf.dodge.startup
        return combo > dodge

    def auto_gauge(self, t):
        self.charge_gauge(self.conf.gauge_val)

    def add_drive_gauge_time(self, delta):
        duration = self.dragondrive_timer.timing - now()
        max_add = 20 - duration
        add_time = min((20 * delta) / self.max_gauge, max_add)
        self.dragondrive_timer.add(add_time)
        duration = self.dragondrive_timer.timing - now()
        if duration <= 0:
            self.d_dragondrive_end(None)
            self.dragon_gauge = 0
        else:
            self.dragon_gauge = (duration / 20) * self.max_gauge
            log('drive_time', f'{add_time:+2.4}', f'{duration:2.4}',
                '{:.2f}%'.format(self.dragon_gauge / self.max_gauge * 100))

    def charge_gauge(self, value, percent=True):
        # if self.status != -1:
        # ignore dragonform blocking gauge (as it would in game) to avoid break-pointy bullshit
        dh = 1 if self.is_dragondrive else self.adv.mod('dh')
        if percent:
            value *= 10
        value = self.adv.sp_convert(dh, value)
        delta = min(self.dragon_gauge + value,
                    self.max_gauge) - self.dragon_gauge
        if self.is_dragondrive and self.dragondrive_buff.get():
            self.add_drive_gauge_time(delta)
        elif delta > 0:
            self.dragon_gauge += delta
            log('dragon_gauge',
                '{:+.2f}%'.format(delta / self.max_gauge * 100),
                '{:.2f}%'.format(self.dragon_gauge / self.max_gauge * 100))

    def dtime(self):
        return self.conf.dshift.startup + self.conf.duration * self.adv.mod(
            'dt') + self.conf.exhilaration * (self.off_ele_mod is None)

    def ddamage(self):
        return self.conf.dracolith + self.adv.mod('da') - 1

    def d_shift_end(self, t):
        if self.action_timer is not None:
            self.action_timer.off()
            self.action_timer = None
        duration = now() - self.shift_start_time
        log(
            self.name, '{:.2f}dmg / {:.2f}s, {:.2f} dps'.format(
                self.shift_damage_sum, duration,
                self.shift_damage_sum / duration), ' '.join(self.act_sum))
        self.act_sum = []
        self.act_list = []
        self.dracolith_mod.off()
        if self.off_ele_mod is not None:
            self.off_ele_mod.off()
        self.skill_use = self.conf.skill_use
        self.shift_silence = True
        Timer(self.end_silence).on(10)
        self.status = -2
        self._setprev()  # turn self from doing to prev
        self._static.doing = self.nop
        self.end_event()
        self.idle_event()

    def d_dragondrive_end(self, t):
        log('dragondrive', 'end')
        self.dragondrive_buff.off()
        Timer(self.end_silence).on(10)
        self.status = -2
        self._setprev()  # turn self from doing to prev
        self._static.doing = self.nop
        self.dragondrive_end_event()
        self.idle_event()

    def act_timer(self, act, time, next_action=None):
        if self.c_act_name == 'dodge':
            self.action_timer = Timer(act, time)
        else:
            self.action_timer = Timer(act, time / self.speed())
        self.action_timer.next_action = next_action
        return self.action_timer.on()

    def d_act_start_t(self, t):
        self.action_timer = None
        self.d_act_start(t.next_action)

    def d_act_start(self, name):
        if name in self.conf and self._static.doing == self and self.action_timer is None:
            self.prev_act = self.c_act_name
            self.prev_conf = self.c_act_conf
            self.c_act_name = name
            self.c_act_conf = self.conf[name]
            self.act_timer(self.d_act_do, self.c_act_conf.startup)

    def d_act_do(self, t):
        if self.c_act_name == 'ds':
            self.skill_use -= 1
            self.ds_event()
            self.shift_damage_sum += self.ds_proc() or 0
            self.shift_end_timer.add(self.conf.ds.startup +
                                     self.conf.ds.recovery)
            self.act_sum.append('s')
        elif self.c_act_name == 'end':
            self.d_shift_end(None)
            self.shift_end_timer.off()
            return
        elif self.c_act_name != 'dodge':
            # dname = self.c_act_name[:-1] if self.c_act_name != 'dshift' else self.c_act_name
            self.shift_damage_sum += self.adv.dmg_make(self.c_act_name,
                                                       self.c_act_conf.dmg)
            if self.c_act_name.startswith('dx'):
                if len(self.act_sum
                       ) > 0 and self.act_sum[-1][0] == 'c' and int(
                           self.act_sum[-1][1]) < int(self.c_act_name[-1]):
                    self.act_sum[-1] = 'c' + self.c_act_name[-1]
                else:
                    self.act_sum.append('c' + self.c_act_name[-1])
        if self.c_act_conf.hit > -1:
            self.adv.hits += self.c_act_conf.hit
        else:
            self.adv.hits = -self.c_act_conf.hit
        self.d_act_next()

    def d_act_next(self):
        if len(self.act_list) > 0:
            nact = self.act_list.pop(0)
        else:
            if self.c_act_name[0:2] == 'dx':
                nact = 'dx{}'.format(int(self.c_act_name[2]) + 1)
                if not nact in self.dx_list:
                    if self.skill_use > 0:
                        nact = 'ds'
                    elif self.dodge_cancel():
                        nact = 'dodge'
                    else:
                        nact = 'dx1'
            else:
                nact = 'dx1'
        if nact == 'ds' or nact == 'dodge' or (
                nact == 'end' and self.c_act_name != 'ds'):  # cancel
            self.act_timer(self.d_act_start_t, self.conf.latency, nact)
        else:  # regular recovery
            self.act_timer(self.d_act_start_t, self.c_act_conf.recovery, nact)

    def parse_act(self, act_str):
        act_str = act_str.strip()
        self.act_list = []
        skill_usage = 0

        for a in act_str.split(' '):
            if a[0] == 'c' or a[0] == 'x':
                for i in range(1, int(a[1]) + 1):
                    dxseq = 'dx{}'.format(i)
                    if dxseq in self.dx_list:
                        self.act_list.append(dxseq)
                if self.dodge_cancel(
                ) or self.act_list[-1] != self.dx_list[-1]:
                    self.act_list.append('dodge')
            else:
                if len(self.act_list) > 0 and self.act_list[-1] == 'dodge':
                    self.act_list.pop()
                if (a == 's' or a == 'ds') and skill_usage < self.skill_use:
                    self.act_list.append('ds')
                    skill_usage += 1
                elif a == 'end':
                    self.act_list.append('end')
                elif a == 'dodge':
                    self.act_list.append('dodge')

    def act(self, act_str):
        self.parse_act(act_str)
        return self()

    def __call__(self):
        if self.disabled or self.shift_silence or self.dragon_gauge < self.shift_cost:
            return False
        doing = self.getdoing()
        if not doing.idle:
            if isinstance(doing, S) or isinstance(doing, DragonForm):
                return False
            if doing.status == -1:
                doing.startup_timer.off()
                log('interrupt', doing.name, 'by ' + self.name,
                    'after {:.2f}s'.format(now() - doing.startup_start))
            elif doing.status == 1:
                doing.recovery_timer.off()
                log('cancel', doing.name, 'by ' + self.name,
                    'after {:.2f}s'.format(now() - doing.recover_start))
        self.shift_count += 1
        if self.is_dragondrive:
            self.act_list = ['end']
            if self.dragondrive_buff.get():
                self.d_dragondrive_end(None)
                return True
            else:
                self.dragondrive_timer.on(20)
                self.dragondrive_buff.on()
        else:
            if len(self.act_list) == 0:
                self.parse_act(self.conf.act)
            self.dragon_gauge -= self.shift_cost
            self.dracolith_mod.mod_value = self.ddamage()
            self.dracolith_mod.on()
            if self.off_ele_mod is not None:
                self.off_ele_mod.on()
        self.shift_damage_sum = 0
        self.status = -1
        self._setdoing()
        self.shift_start_time = now()
        self.shift_end_timer.on(self.dtime())
        self.shift_event()
        log('cast', 'dshift', self.name)
        self.d_act_start('dshift')
        return True
Example #5
0
class DragonForm(Action):
    def __init__(self, name, conf, adv, ds_proc):
        self.name = name
        self.conf = conf
        self.adv = adv
        self.cancel_by = []
        self.interrupt_by = []
        self.disabled = False
        self.shift_event = Event('dragon')
        self.end_event = Event('dragon_end')

        self.ds_proc = ds_proc
        self.ds_reset()
        self.act_list = []
        self.act_sum = []

        self.dx_list = [
            'dx{}'.format(i) for i in range(1, 6)
            if 'dmg' in self.conf['dx{}'.format(i)]
        ]

        self.ds_event = Event('s')
        self.ds_event.name = 'ds'

        self.action_timer = None

        self.shift_start_time = 0
        self.shift_damage_sum = 0
        self.shift_end_timer = Timer(self.d_shift_end)
        self.idle_event = Event('idle')

        self.c_act_name = None
        self.c_act_conf = None
        self.dracolith_mod = self.adv.Modifier('dracolith', 'att', 'dragon', 0)
        self.dracolith_mod.off()
        self.off_ele_mod = None
        if self.adv.slots.c.ele != self.adv.slots.d.ele:
            self.off_ele_mod = self.adv.Modifier('off_ele', 'att', 'dragon',
                                                 -1 / 3)
            self.off_ele_mod.off()

        self.dragon_gauge = 0
        self.dragon_gauge_val = self.conf.gauge_val
        self.dragon_gauge_timer = Timer(self.auto_gauge,
                                        timeout=max(1, self.conf.gauge_iv),
                                        repeat=1).on()
        self.dragon_gauge_timer_diff = 0
        self.max_gauge = 1000
        self.shift_cost = 500

        self.shift_count = 0
        self.shift_silence = False

        self.is_dragondrive = False

    def set_dragondrive(self,
                        dd_buff,
                        max_gauge=3000,
                        shift_cost=1200,
                        drain=150):
        self.disabled = False
        self.is_dragondrive = True
        self.shift_event = Event('dragondrive')
        self.dragondrive_end_event = Event('dragondrive_end')
        ratio = max_gauge / self.max_gauge
        self.dragon_gauge *= ratio
        self.dragon_gauge_val *= ratio
        self.max_gauge = max_gauge
        self.shift_cost = shift_cost  # does not deduct, but need to have this much pt to shift
        self.drain = drain
        self.dragondrive_buff = dd_buff
        self.dragondrive_timer = Timer(self.d_dragondrive_end)
        return self.dragondrive_buff

    def end_silence(self, t):
        self.shift_silence = False

    def dodge_cancel(self):
        if len(self.dx_list) <= 0:
            return False
        combo = self.conf[self.dx_list[-1]].recovery / self.speed()
        dodge = self.conf.dodge.startup
        return combo > dodge

    def auto_gauge(self, t):
        self.charge_gauge(self.dragon_gauge_val)

    def pause_auto_gauge(self):
        self.dragon_gauge_timer_diff = self.dragon_gauge_timer.timing - now()
        self.dragon_gauge_timer.off()

    def resume_auto_gauge(self, t):
        self.auto_gauge(t)
        self.dragon_gauge_timer.on()

    def add_drive_gauge_time(self, delta, skill_pause=False):
        max_duration = self.max_gauge / self.drain
        duration = self.dragondrive_timer.timing - now()
        max_add = max_duration - duration
        if skill_pause:
            add_time = min(delta, max_add)
        else:
            add_time = min(delta / self.drain, max_add)
        self.dragondrive_timer.add(add_time)
        duration = self.dragondrive_timer.timing - now()
        if duration <= 0:
            self.d_dragondrive_end('<gauge deplete>')
            self.dragon_gauge = 0
        else:
            self.dragon_gauge = (duration / max_duration) * self.max_gauge
            if add_time > 0:
                log('drive_time' if not skill_pause else 'skill_pause',
                    f'{add_time:+2.4}', f'{duration:2.4}',
                    f'{int(self.dragon_gauge)}/{int(self.max_gauge)}')

    def charge_gauge(self, value, utp=False, dhaste=True):
        dhaste = not utp
        dh = self.adv.mod('dh') if dhaste else 1
        if not utp:
            value *= 10
        value = self.adv.sp_convert(dh, value)
        delta = min(self.dragon_gauge + value,
                    self.max_gauge) - self.dragon_gauge
        if self.is_dragondrive and self.dragondrive_buff.get():
            self.add_drive_gauge_time(delta)
        elif delta > 0:
            self.dragon_gauge += delta
            if utp:
                log('dragon_gauge', '{:+} utp'.format(int(delta)),
                    f'{int(self.dragon_gauge)}/{int(self.max_gauge)}')
            else:
                log('dragon_gauge',
                    '{:+.2f}%'.format(delta / self.max_gauge * 100),
                    '{:.2f}%'.format(self.dragon_gauge / self.max_gauge * 100))

    def dtime(self):
        return self.conf.dshift.startup + self.conf.duration * self.adv.mod(
            'dt') + self.conf.exhilaration * (self.off_ele_mod is None)

    def ddamage(self):
        return self.conf.dracolith + self.adv.mod('da') - 1

    def ds_check(self):
        return self.skill_use > 0 and self.skill_spc >= self.skill_sp

    def ds_reset(self):
        self.skill_use = self.conf.skill_use
        self.skill_sp = 0 if self.conf.skill_use == 1 else 30
        self.skill_spc = self.skill_sp

    def d_shift_end(self, t):
        if self.action_timer is not None:
            self.action_timer.off()
            self.action_timer = None
        duration = now() - self.shift_start_time
        log(
            self.name, '{:.2f}dmg / {:.2f}s, {:.2f} dps'.format(
                self.shift_damage_sum, duration,
                self.shift_damage_sum / duration), ' '.join(self.act_sum))
        self.act_sum = []
        self.act_list = []
        self.dracolith_mod.off()
        if self.off_ele_mod is not None:
            self.off_ele_mod.off()
        self.ds_reset()
        if not self.is_dragondrive:
            self.shift_silence = True
            Timer(self.end_silence).on(10)
            Timer(self.resume_auto_gauge).on(self.dragon_gauge_timer_diff)
        self.status = -2
        self._setprev()  # turn self from doing to prev
        self._static.doing = self.nop
        self.end_event()
        self.idle_event()

    def d_dragondrive_end(self, t):
        log('dragondrive', 'end', t if isinstance(t, str) else '<timeout>')
        self.dragondrive_buff.off()
        self.shift_silence = True
        Timer(self.end_silence).on(10)
        self.status = -2
        self._setprev()  # turn self from doing to prev
        self._static.doing = self.nop
        self.dragondrive_end_event()
        self.idle_event()

    def act_timer(self, act, time, next_action=None):
        if self.c_act_name == 'dodge':
            self.action_timer = Timer(act, time)
        else:
            self.action_timer = Timer(act, time / self.speed())
        self.action_timer.next_action = next_action
        return self.action_timer.on()

    def d_act_start_t(self, t):
        self.action_timer = None
        self.d_act_start(t.next_action)

    def d_act_start(self, name):
        if name in self.conf and self._static.doing == self and self.action_timer is None:
            self.prev_act = self.c_act_name
            self.prev_conf = self.c_act_conf
            self.c_act_name = name
            self.c_act_conf = self.conf[name]
            self.act_timer(self.d_act_do, self.c_act_conf.startup)

    def d_act_do(self, t):
        if self.c_act_name == 'ds':
            self.skill_use -= 1
            self.skill_spc = 0
            self.ds_event()
            self.shift_damage_sum += self.ds_proc() or 0
            self.shift_end_timer.add(self.conf.ds.startup +
                                     self.conf.ds.recovery)
            self.act_sum.append('s')
        elif self.c_act_name == 'end':
            self.d_shift_end(None)
            self.shift_end_timer.off()
            return
        elif self.c_act_name != 'dodge':
            # dname = self.c_act_name[:-1] if self.c_act_name != 'dshift' else self.c_act_name
            self.shift_damage_sum += self.adv.dmg_make(
                self.c_act_name, self.c_act_conf.dmg,
                'x' if self.c_act_name != 'dshift' else self.c_act_name)
            if self.c_act_name.startswith('dx'):
                if len(self.act_sum
                       ) > 0 and self.act_sum[-1][0] == 'c' and int(
                           self.act_sum[-1][1]) < int(self.c_act_name[-1]):
                    self.act_sum[-1] = 'c' + self.c_act_name[-1]
                else:
                    self.act_sum.append('c' + self.c_act_name[-1])
                if self.skill_sp > 0 and self.skill_spc < self.skill_sp:
                    self.skill_spc += self.adv.sp_convert(
                        self.adv.sp_mod('x'), 5)
                    if self.skill_spc > self.skill_sp:
                        self.skill_spc = self.skill_sp
                    log(self.c_act_name, 'sp',
                        f'{self.skill_spc}/{self.skill_sp}')
        if self.c_act_conf.hit > -1:
            self.adv.hits += self.c_act_conf.hit
        else:
            self.adv.hits = -self.c_act_conf.hit
        self.d_act_next()

    def d_act_next(self):
        nact = None
        if len(self.act_list) > 0:
            if self.act_list[0] != 'ds' or self.ds_check():
                nact = self.act_list.pop(0)
        if nact is None:
            if self.c_act_name[0:2] == 'dx':
                nact = 'dx{}'.format(int(self.c_act_name[2]) + 1)
                if not nact in self.dx_list:
                    if self.ds_check():
                        nact = 'ds'
                    elif self.dodge_cancel():
                        nact = 'dodge'
                    else:
                        nact = 'dx1'
            else:
                nact = 'dx1'
        if nact == 'ds' or nact == 'dodge' or (
                nact == 'end' and self.c_act_name != 'ds'):  # cancel
            self.act_timer(self.d_act_start_t, self.conf.latency, nact)
        else:  # regular recovery
            self.act_timer(self.d_act_start_t, self.c_act_conf.recovery, nact)

    def parse_act(self, act_str):
        act_str = act_str.strip()
        self.act_list = []
        skill_usage = 0

        for a in act_str.split(' '):
            if a[0] == 'c' or a[0] == 'x':
                for i in range(1, int(a[1]) + 1):
                    dxseq = 'dx{}'.format(i)
                    if dxseq in self.dx_list:
                        self.act_list.append(dxseq)
                if self.dodge_cancel(
                ) or self.act_list[-1] != self.dx_list[-1]:
                    self.act_list.append('dodge')
            else:
                if len(self.act_list) > 0 and self.act_list[-1] == 'dodge':
                    self.act_list.pop()
                if (a == 's' or a == 'ds') and skill_usage < self.skill_use:
                    self.act_list.append('ds')
                    skill_usage += 1
                elif a == 'end':
                    self.act_list.append('end')
                elif a == 'dodge':
                    self.act_list.append('dodge')

    def act(self, act_str):
        self.parse_act(act_str)
        return self()

    def check(self, dryrun=True):
        if self.disabled or self.shift_silence:
            return False
        if self.dragon_gauge < self.shift_cost and not (
                self.is_dragondrive and self.dragondrive_buff.get()):
            return False
        doing = self.getdoing()
        if not doing.idle:
            if isinstance(doing, S) or isinstance(doing, DragonForm):
                return False
            if not dryrun:
                if doing.status == -1:
                    doing.startup_timer.off()
                    log('interrupt', doing.name, 'by ' + self.name,
                        'after {:.2f}s'.format(now() - doing.startup_start))
                elif doing.status == 1:
                    doing.recovery_timer.off()
                    log('cancel', doing.name, 'by ' + self.name,
                        'after {:.2f}s'.format(now() - doing.recover_start))
        return True

    def __call__(self):
        if not self.check(dryrun=False):
            return False
        self.shift_count += 1
        if self.is_dragondrive:
            self.act_list = ['end']
            if self.dragondrive_buff.get():
                self.d_dragondrive_end('<turn off>')
                return True
            else:
                self.dragondrive_timer.on(self.max_gauge / self.drain)
                self.dragondrive_buff.on()
        else:
            if len(self.act_list) == 0:
                self.parse_act(self.conf.act)
            self.dragon_gauge -= self.shift_cost
            self.dracolith_mod.mod_value = self.ddamage()
            self.dracolith_mod.on()
            if self.off_ele_mod is not None:
                self.off_ele_mod.on()
            self.pause_auto_gauge()
        self.shift_damage_sum = 0
        self.status = -1
        self._setdoing()
        self.shift_start_time = now()
        self.shift_end_timer.on(self.dtime())
        self.shift_event()
        log('cast', 'dshift', self.name)
        self.d_act_start('dshift')
        return True
Example #6
0
class DragonForm(Action):
    def __init__(self, name, conf, adv):
        self.name = name
        self.conf = conf
        self.adv = adv
        self.disabled = False
        self.shift_event = Event("dragon")
        self.act_event = Event("dact")
        self.end_event = Event("dragon_end")
        self.delayed = set()

        self.ds_reset()
        self.act_list = []
        self.act_sum = []
        self.repeat_act = False

        self.dx_list = [dx for dx, _ in self.conf.find(r"^dx\d+$")]

        self.ds_event = Event("ds")
        self.ds_event.name = "ds"
        self.dx_event = Event("dx")

        self.action_timer = None

        self.shift_start_time = 0
        self.shift_end_timer = Timer(self.d_shift_end)
        self.idle_event = Event("idle")

        self.c_act_name = None
        self.c_act_conf = None
        self.dracolith_mod = self.adv.Modifier("dracolith", "ex", "dragon", 0)
        self.dracolith_mod.get = self.ddamage
        self.dracolith_mod.off()
        self.shift_mods = [self.dracolith_mod]
        self.shift_spd_mod = None
        self.off_ele = self.adv.slots.c.ele != self.adv.slots.d.ele

        self.dragon_gauge = 0
        self.dragon_gauge_val = self.conf.gauge_val
        self.conf.gauge_iv = min(int(self.adv.duration / 12), 15)
        self.dragon_gauge_timer = Timer(self.auto_gauge,
                                        timeout=max(1, self.conf.gauge_iv),
                                        repeat=1).on()
        self.dragon_gauge_pause_timer = None
        self.dragon_gauge_timer_diff = 0
        self.max_gauge = 1000
        self.shift_cost = 500

        self.shift_count = 0
        self.shift_silence = False

        self.is_dragondrive = False
        self.can_end = True

        self.allow_end_cd = self.conf.allow_end + self.dstime()
        self.allow_force_end_timer = Timer(self.set_allow_end,
                                           timeout=self.allow_end_cd)
        self.allow_end = False

    def set_shift_end(self, value, percent=True):
        if self.can_end:
            max_d = self.dtime() - self.conf.dshift.startup
            cur_d = self.shift_end_timer.timing - now() - (
                self.conf.ds.uses - self.skill_use) * self.dstime()
            delta_t = value
            if percent:
                delta_t *= max_d
            delta_t = min(max_d, cur_d + delta_t) - cur_d
            if cur_d + delta_t > 0:
                self.shift_end_timer.add(delta_t)
                log("shift_time", f"{delta_t:+2.4}", f"{cur_d+delta_t:2.4}")
            else:
                self.d_shift_end(None)
                self.shift_end_timer.off()

    def can_interrupt(self, *args):
        return None

    def can_cancel(self, *args):
        return None

    def getstartup(self):
        return 0

    def getrecovery(self):
        return 0

    def reset_allow_end(self):
        if self.is_dragondrive:
            self.allow_end = True
        else:
            log("allow_end", self.allow_end_cd)
            self.allow_end = False
            self.allow_force_end_timer = Timer(self.set_allow_end,
                                               timeout=self.allow_end_cd)
            self.allow_force_end_timer.on()
            self.allow_end_cd = min(
                self.allow_end_cd + self.conf.allow_end_step, self.dtime())

    def set_allow_end(self, _):
        self.allow_end = True

    def set_dragondrive(self,
                        dd_buff,
                        max_gauge=3000,
                        shift_cost=1200,
                        drain=150):
        self.disabled = False
        self.is_dragondrive = True
        self.shift_event = Event("dragondrive")
        self.end_event = Event("dragondrive_end")
        ratio = max_gauge / self.max_gauge
        self.dragon_gauge *= ratio
        self.max_gauge = max_gauge
        self.shift_cost = shift_cost  # does not deduct, but need to have this much pt to shift
        self.drain = drain
        self.dragondrive_buff = dd_buff
        self.dragondrive_timer = Timer(self.d_dragondrive_end)
        return self.dragondrive_buff

    def set_dragonbattle(self, duration):
        self.disabled = False
        self.dragon_gauge = self.max_gauge
        self.conf.duration = duration
        self.can_end = False
        self.repeat_act = True
        if self.conf.ds["sp_db"]:
            self.skill_sp = self.conf.ds["sp_db"]
        else:
            self.skill_sp = self.conf.ds.sp + 15
        self.skill_spc = self.skill_sp
        self.skill_use = -1
        self.skill_use_final = -1

    def end_silence(self, t):
        self.shift_silence = False

    def dodge_cancel(self):
        if len(self.dx_list) <= 0:
            return False
        combo = self.conf[self.dx_list[-1]].recovery / self.speed()
        dodge = self.conf.dodge.startup + self.conf.dodge.recovery
        return combo > dodge

    def auto_gauge(self, t):
        self.charge_gauge(self.dragon_gauge_val, percent=True, auto=True)

    def pause_auto_gauge(self):
        if self.dragon_gauge_pause_timer is None:
            self.dragon_gauge_timer_diff = self.dragon_gauge_timer.timing - now(
            )
        else:
            self.dragon_gauge_timer_diff = self.dragon_gauge_pause_timer.timing - now(
            )
        self.dragon_gauge_timer.off()

    def resume_auto_gauge(self, t):
        self.dragon_gauge_pause_timer = None
        self.auto_gauge(t)
        self.dragon_gauge_timer.on()

    def add_drive_gauge_time(self, delta, skill_pause=False):
        max_duration = self.max_gauge / self.drain
        duration = self.dragondrive_timer.timing - now()
        max_add = max_duration - duration
        if skill_pause:
            add_time = min(delta, max_add)
        else:
            add_time = min(delta / self.drain, max_add)
        duration = self.dragondrive_timer.add(add_time)
        if duration <= 0:
            self.d_dragondrive_end("<gauge deplete>")
        else:
            self.dragon_gauge = (duration / max_duration) * self.max_gauge
            if add_time != 0:
                log(
                    "drive_time" if not skill_pause else "skill_pause",
                    f"{add_time:+2.4}",
                    f"{duration:2.4}",
                    f"{int(self.dragon_gauge)}/{int(self.max_gauge)}",
                )

    def charge_gauge(self,
                     value,
                     utp=False,
                     dhaste=True,
                     percent=False,
                     auto=False):
        # if dhaste is None:
        #     dhaste = not utp
        dh = self.dhaste() if dhaste else 1
        if utp:
            dh *= self.adv.mod("utph", operator=operator.add)
        if percent:
            value *= self.max_gauge / 100
        value = self.adv.sp_convert(dh, value)
        delta = min(self.dragon_gauge + value,
                    self.max_gauge) - self.dragon_gauge
        if self.is_dragondrive and self.dragondrive_buff.get():
            self.add_drive_gauge_time(delta)
        elif delta != 0:
            self.dragon_gauge += delta
            log(
                "dragon_gauge",
                "{:+} ({:+.2f}%)".format(int(delta),
                                         delta / self.max_gauge * 100),
                f"{int(self.dragon_gauge)}/{int(self.max_gauge)}",
                "{:.2f}%".format(self.dragon_gauge / self.max_gauge * 100),
                "auto" if auto else "",
            )
        return value

    @allow_acl
    def dtime(self):
        return self.conf.dshift.startup + self.conf.duration * self.adv.mod(
            "dt") + self.conf.exhilaration * int(not self.off_ele)

    def dstime(self):
        try:
            return (self.conf.ds.startup +
                    self.conf.ds.recovery) / self.speed()
        except TypeError:
            return 0

    def dhaste(self):
        return self.adv.mod("dh", operator=operator.add)

    def chain_dhaste(self):
        return self.adv.sub_mod("dh", "chain") + 1

    @allow_acl
    def ddamage(self):
        return self.conf.dracolith + self.adv.mod(
            "da", operator=operator.add, initial=0)

    def ds_check(self):
        return self.skill_use != 0 and self.skill_spc >= self.skill_sp and self.shift_end_timer.elapsed(
        ) >= 1.9

    def ds_charge(self, value):
        if self.skill_use != 0 and self.skill_spc < self.skill_sp:
            self.skill_spc += self.adv.sp_convert(self.adv.sp_mod("x"), value)
            if self.skill_spc > self.skill_sp:
                self.skill_spc = self.skill_sp
            log(self.c_act_name, "sp", f"{self.skill_spc}/{self.skill_sp}")

    def ds_reset(self):
        self.skill_use = self.conf.ds.uses
        self.skill_use_final = 1 if self.conf["ds_final"] else 0
        self.skill_sp = self.conf.ds.sp
        self.skill_spc = self.skill_sp

    def d_shift_partial_end(self):
        if not self.is_dragondrive and self.status != Action.OFF:
            duration = now() - self.shift_start_time
            shift_dmg = g_logs.shift_dmg
            g_logs.log_shift_dmg(False)
            log(
                self.name,
                "{:.2f}dmg / {:.2f}s, {:.2f} dps".format(
                    shift_dmg, duration, shift_dmg / duration),
                " ".join(self.act_sum),
            )

    def d_shift_end(self, t):
        if self.action_timer is not None:
            self.action_timer.off()
            self.action_timer = None
        if not self.is_dragondrive and self.prev_act != "ds" and self.skill_use_final > 0:
            self.skill_use_final -= 1
            self.d_act_start("ds_final")
            self.act_list = ["end"]
            return False
        duration = now() - self.shift_start_time
        shift_dmg = g_logs.shift_dmg
        g_logs.log_shift_dmg(False)
        count = self.clear_delayed()
        if count > 0:
            log(
                "cancel",
                self.c_act_name,
                f"by shift end",
                f'lost {count} hit{"s" if count > 1 else ""}',
            )
        log(
            self.name,
            "{:.2f}dmg / {:.2f}s, {:.2f} dps".format(shift_dmg, duration,
                                                     shift_dmg / duration),
            " ".join(self.act_sum),
        )
        self.act_sum = []
        self.act_list = []
        if self.off_ele:
            self.adv.element = self.adv.slots.c.ele
        if self.shift_spd_mod is not None:
            self.shift_spd_mod.off()
        self.ds_reset()
        if not self.is_dragondrive:
            self.shift_silence = True
            Timer(self.end_silence).on(10)
            self.dragon_gauge_pause_timer = Timer(self.resume_auto_gauge).on(
                self.dragon_gauge_timer_diff)
        self.status = Action.OFF
        self._setprev()  # turn self from doing to prev
        self._static.doing = self.nop
        if not self.is_dragondrive:
            self.end_event()
        self.idle_event()
        return True

    def d_dragondrive_end(self, t):
        self.dragon_gauge = 0
        log("dragondrive", "end", t if isinstance(t, str) else "<timeout>")
        self.dragondrive_buff.off()
        self.shift_silence = True
        Timer(self.end_silence).on(10)
        self.status = Action.OFF
        self._setprev()  # turn self from doing to prev
        self._static.doing = self.nop
        self.end_event()
        self.idle_event()

    def act_timer(self, act, time, next_action=None):
        if self.c_act_name == "dodge":
            self.action_timer = Timer(act, time)
        else:
            self.action_timer = Timer(act, time / self.speed())
        self.action_timer.next_action = next_action
        return self.action_timer.on()

    def d_act_start_t(self, t):
        self.action_timer = None
        self.d_act_start(t.next_action)

    def d_act_start(self, name):
        if name in self.conf and self._static.doing == self and self.action_timer is None:
            log("d_act", name)
            self.prev_act = self.c_act_name
            self.prev_conf = self.c_act_conf
            self.c_act_name = name
            self.c_act_conf = self.conf[name]
            self.act_timer(self.d_act_do, self.c_act_conf.startup)

    def d_act_do_hitattr(self, act_name):
        actconf = self.conf[act_name]
        e = self.act_event
        e.name = act_name
        e.base = act_name
        e.group = "dragon"
        self.adv.actmod_on(e)
        try:
            getattr(self.adv, f"{act_name}_before")(e)
        except AttributeError:
            pass

        final_mt = self.adv.schedule_hits(e, self.conf[act_name])
        if final_mt:
            final_mt.actmod = True
            final_mt.actmod = True
            try:
                final_mt.proc = getattr(self.adv, f"{act_name}_proc")
            except AttributeError:
                pass
        else:
            self.adv.actmod_off(e)
            try:
                getattr(self.adv, f"{act_name}_proc")(e)
            except AttributeError:
                pass

    def d_act_do(self, t):
        if self.c_act_name == "end":
            if self.d_shift_end(None):
                self.shift_end_timer.off()
            return
        self.d_act_do_hitattr(self.c_act_name)
        if self.c_act_name in ("ds", "ds_final"):
            self.skill_use -= 1
            self.skill_spc = 0
            self.act_sum.append("s")
            self.ds_event()
            self.shift_end_timer.add(self.dstime())
        elif self.c_act_name.startswith("dx"):
            if len(self.act_sum) > 0 and self.act_sum[-1][0] == "c" and int(
                    self.act_sum[-1][1]) < int(self.c_act_name[-1]):
                self.act_sum[-1] = "c" + self.c_act_name[-1]
            else:
                self.act_sum.append("c" + self.c_act_name[-1])
            self.dx_event.index = int(self.c_act_name[-1])
            self.dx_event()

        self.d_act_next()

    def d_act_next(self):
        nact = None
        if self.repeat_act and not self.act_list:
            self.parse_act(self.conf.act)
        if self.act_list:
            if self.act_list[0] not in ("ds", "dsf") or self.ds_check():
                if self.act_list[0] == "end" and not self.allow_end:
                    nact = None
                else:
                    nact = self.act_list.pop(0)
            # print('CHOSE BY LIST', nact, self.c_act_name)
        if nact is None:
            if self.c_act_name[0:2] == "dx":
                nact = "dx{}".format(int(self.c_act_name[2]) + 1)
                if not nact in self.dx_list:
                    if self.ds_check():
                        nact = "ds"
                    elif self.dodge_cancel():
                        nact = "dodge"
                    else:
                        nact = "dx1"
            else:
                nact = "dx1"
            # print('CHOSE BY DEFAULT', nact, self.c_act_name)
        if self.has_delayed and nact == "dsf":
            nact = "ds"
            count = self.clear_delayed()
            if count > 0:
                log(
                    "cancel",
                    self.c_act_name,
                    f"by {nact}",
                    f'lost {count} hit{"s" if count > 1 else ""}',
                )
            return self.act_timer(self.d_act_start_t, self.conf.latency, nact)
        if nact in ("ds", "dsf",
                    "dodge") or (nact == "end" and self.c_act_name not in
                                 ("ds", "ds_final", "dshift")):  # cancel
            if nact == "dsf":
                nact = "ds"
            self.act_timer(self.d_act_start_t,
                           self.max_delayed + self.conf.latency, nact)
        else:  # regular recovery
            self.act_timer(self.d_act_start_t, self.c_act_conf.recovery, nact)

    def parse_act(self, act_str):
        if self.status != Action.OFF and not self.repeat_act:
            return
        act_str = act_str.strip()
        self.act_list = []
        skill_usage = 0

        for a in act_str.split("-"):
            if a[0] == "c" or a[0] == "x":
                for i in range(1, int(a[1]) + 1):
                    dxseq = "dx{}".format(i)
                    if dxseq in self.dx_list:
                        self.act_list.append(dxseq)
                try:
                    if self.dodge_cancel(
                    ) or self.act_list[-1] != self.dx_list[-1]:
                        self.act_list.append("dodge")
                except IndexError:
                    pass
            else:
                nact = None
                if (a in ("s", "ds", "sf",
                          "dsf")) and (self.skill_use <= -1
                                       or skill_usage < self.skill_use):
                    if a[-1] == "f":
                        # self.act_list.append('dsf')
                        nact = "dsf"
                    else:
                        nact = "ds"
                    skill_usage += 1
                elif a == "end" and self.can_end:
                    nact = "end"
                elif a == "dodge":
                    nact = "dodge"
                if nact:
                    try:
                        if len(self.act_list
                               ) > 0 and self.act_list[-1] == "dodge":
                            self.act_list.pop()
                    except IndexError:
                        pass
                    self.act_list.append(nact)

    def act(self, act_str):
        self.parse_act(act_str)
        return self()

    @allow_acl
    def check(self, dryrun=True):
        if self.disabled or self.shift_silence:
            return False
        if self.dragon_gauge < self.shift_cost and not (
                self.is_dragondrive and self.dragondrive_buff.get()):
            return False
        doing = self.getdoing()
        if not doing.idle:
            if isinstance(doing, S) or isinstance(doing, DragonForm):
                return False
            if dryrun == False:
                if doing.status == Action.STARTUP:
                    doing.startup_timer.off()
                    doing.end_event()
                    log(
                        "interrupt",
                        doing.name,
                        "by " + self.name,
                        "after {:.2f}s".format(now() - doing.startup_start),
                    )
                elif doing.status == Action.RECOVERY:
                    doing.recovery_timer.off()
                    doing.end_event()
                    log(
                        "cancel",
                        doing.name,
                        "by " + self.name,
                        "after {:.2f}s".format(now() - doing.recover_start),
                    )
        return True

    def __call__(self):
        if not self.check(dryrun=False):
            return False
        if self.is_dragondrive:
            self.act_list = ["end"]
            if self.dragondrive_buff.get():
                self.d_dragondrive_end("<turn off>")
                return True
            else:
                log(
                    "cast",
                    "dragondrive",
                    self.name,
                    f"base duration {self.dragon_gauge/self.drain:.4}s",
                )
                self.dragondrive_timer.on(self.dragon_gauge / self.drain)
                self.dragondrive_buff.on()
        else:
            log("cast", "dshift", self.name)
            if len(self.act_list) == 0:
                self.parse_act(self.conf.act)
            self.dragon_gauge -= self.shift_cost
            if self.off_ele:
                self.adv.element = self.adv.slots.d.ele
            if self.shift_spd_mod is not None:
                self.shift_spd_mod.on()
            self.pause_auto_gauge()
        self.shift_count += 1
        self.status = Action.STARTUP
        self._setdoing()
        g_logs.log_shift_dmg(True)
        self.shift_start_time = now()
        self.shift_end_timer.on(self.dtime())
        self.reset_allow_end()
        self.shift_event()
        self.d_act_start("dshift")
        return True
Example #7
0
class Buff(object):
    MAXHP_BUFF_CAP = 0.30
    _static = Static({"all_buffs": [], "adv": None})
    DB_DURATION = 15  # usual doublebuff effect duration for offensive buffs, note that regen lasts 20s

    def __init__(
        self,
        name="<buff_noname>",
        value=0,
        duration=0,
        mtype="att",
        morder=None,
        modifier=None,
        hidden=False,
        source=None,
    ):
        self.name = name
        self.__value = value
        self.duration = duration
        self.mod_type = mtype
        self.mod_order = morder or ("chance"
                                    if self.mod_type == "crit" else "buff")
        self.bufftype = "misc" if hidden else "self"

        self.source = source
        if self.source is not None and source[0] != "s" and source[0:2] != "ds":
            self.bufftime = self._ex_bufftime
        else:
            self.bufftime = self._bufftime
        self.buff_end_timer = Timer(self.buff_end_proc)
        if modifier:
            self.modifier = modifier
            self.get = self.modifier.get
        elif mtype != "effect":
            self.modifier = Modifier("mod_" + self.name, self.mod_type,
                                     self.mod_order, 0)
            self.modifier.get = self.get
        else:
            self.modifier = None
        self.dmg_test_event = Event("dmg_formula")
        self.dmg_test_event.dmg_coef = 1
        self.dmg_test_event.dname = "test"

        self.hidden = hidden

        self.__stored = 0
        self.__active = 0

        self.buffevent = Event("buff")
        self.pause_time = -1
        self.refresh_time = -1
        # self.on()

    def logwrapper(self, *args):
        if not self.hidden:
            log("buff", *args)

    def _no_bufftime(self):
        return 1

    def _ex_bufftime(self):
        return 1 + self._static.adv.sub_mod("buff", "ex")

    def _bufftime(self):
        return self._static.adv.mod("buff", operator=operator.add)

    def _debufftime(self):
        return self._static.adv.mod("debuff", operator=operator.add)

    def any_bufftime(self):
        self.bufftime = self._bufftime
        return self

    def no_bufftime(self):
        self.bufftime = self._no_bufftime
        return self

    def ex_bufftime(self):
        self.bufftime = self._ex_bufftime
        return self

    def value(self, newvalue=None):
        if newvalue is not None:
            self.logwrapper(
                self.name,
                f"{self.mod_type}({self.mod_order}): {newvalue:.02f}",
                "buff value change",
            )
            return self.set(newvalue)
        else:
            return self.__value

    @allow_acl
    def get(self):
        if self.__active:
            return self.__value
        else:
            return 0

    def set(self, v, d=None):
        self.__value = v
        if d != None:
            self.duration = d
        return self

    def stack(self):
        stack = 0
        for i in self._static.all_buffs:
            if i.name == self.name:
                if i.__active != 0:
                    stack += 1
        return stack

    def valuestack(self):
        stack = 0
        value = 0
        for i in self._static.all_buffs:
            if i.name == self.name:
                if i.__active != 0:
                    stack += 1
                    value += i.__value
        return value, stack

    def effect_on(self):
        value = self.get()
        if self.mod_type == "defense" and value > 0:
            db = Event("defchain")
            db.source = self.source
            db.on()
            if self.bufftype == "team":
                log("buff", "doublebuff", 15 * self.bufftime())
                if self.bufftime == self._bufftime:
                    self._static.adv.slots.c.set_need_bufftime()
        elif self.mod_type == "maxhp":
            if self._static.adv.sub_mod("maxhp", "buff") < Buff.MAXHP_BUFF_CAP:
                self.modifier.on()
            percent = value * 100
            log("heal", self.name, self._static.adv.max_hp * value,
                "team" if self.bufftype == "team" else "self")
            self._static.adv.add_hp(percent)
        # FIXME: heal formula 1day twust
        elif self.mod_type == "regen" and value != 0:
            self.set_hp_event = Event("set_hp")
            self.set_hp_event.delta = value
            self.regen_timer = Timer(self.hp_regen, 3.9, True).on()
        elif self.mod_type == "heal" and value != 0:
            self.set_hp_event = Event("heal_make")
            self.set_hp_event.name = self.name
            self.set_hp_event.delta = self._static.adv.heal_formula(
                self.source, value)
            self.set_hp_event.target = "team" if self.bufftype == "team" else "self"
            self.regen_timer = Timer(self.hp_regen, 2.9, True).on()
        else:
            return self.modifier and self.modifier.on()

    def effect_off(self):
        if self.mod_type in ("regen", "heal"):
            self.regen_timer.off()
        else:
            return self.modifier and self.modifier.off()

    def buff_end_proc(self, e):
        self.logwrapper(
            self.name,
            f"{self.mod_type}({self.mod_order}): {self.value():.02f}",
            "buff end <timeout>",
        )
        self.__active = 0

        if self.__stored:
            self._static.all_buffs.remove(self)
            self.__stored = 0
        value, stack = self.valuestack()
        if stack > 0:
            self.logwrapper(
                self.name,
                f"{self.mod_type}({self.mod_order}): {value:.02f}",
                f"buff stack <{stack}>",
            )
        self.effect_off()

    def count_team_buff(self):
        if self.bufftype == "self":
            return
        base_mods = [
            Modifier("base_cc", "crit", "chance", 0.12),
            Modifier("base_killer", "killer", "passive", 0.30),
        ]
        self.dmg_test_event.modifiers = ModifierDict()
        for mod in base_mods:
            self.dmg_test_event.modifiers.append(mod)
        for b in filter(lambda b: b.get() and b.bufftype == "simulated_def",
                        self._static.all_buffs):
            self.dmg_test_event.modifiers.append(b.modifier)

        self.dmg_test_event()
        no_team_buff_dmg = self.dmg_test_event.dmg

        placeholders = []
        for b in filter(
                lambda b: b.get() and b.bufftype in ("team", "debuff"),
                self._static.all_buffs,
        ):
            placehold = None
            if b.modifier.mod_type == "s":
                placehold = Modifier("placehold_sd", "att", "sd",
                                     b.modifier.get() / 2)
            elif b.modifier.mod_type == "spd":
                placehold = Modifier("placehold_spd", "att", "spd",
                                     b.modifier.get())
            elif b.modifier.mod_type.endswith("_killer"):
                placehold = Modifier("placehold_k", "killer", "passive",
                                     b.modifier.get())
            if placehold:
                self.dmg_test_event.modifiers.append(placehold)
                placeholders.append(placehold)
            else:
                self.dmg_test_event.modifiers.append(b.modifier)

        self.dmg_test_event()
        team_buff_dmg = self.dmg_test_event.dmg
        log("buff", "team", team_buff_dmg / no_team_buff_dmg - 1)

        for mod in chain(base_mods, placeholders):
            mod.off()

    def on(self, duration=None):
        d = max(-1, (duration or self.duration) * self.bufftime())
        if d != -1 and self.bufftime == self._bufftime:
            self._static.adv.slots.c.set_need_bufftime()
        if self.__active == 0:
            self.__active = 1
            if self.__stored == 0:
                self._static.all_buffs.append(self)
                self.__stored = 1
            if d >= 0:
                self.buff_end_timer.on(d)
            proc_type = "start"
        else:
            if d >= 0:
                if self.buff_end_timer.online:
                    self.buff_end_timer.on(d)
                else:
                    self.refresh_time = d
            else:
                return self
            proc_type = "refresh"

        self.logwrapper(
            self.name,
            f"{self.mod_type}({self.mod_order}): {self.value():.02f}",
            f"buff {proc_type} <{d:.02f}s>",
        )
        value, stack = self.valuestack()
        if stack > 1:
            log(
                "buff",
                self.name,
                f"{self.mod_type}({self.mod_order}): {value:.02f}",
                f"buff stack <{stack}>",
            )

        self.effect_on()

        self.buffevent.buff = self
        self.buffevent.on()

        return self

    def hp_regen(self, t):
        self.set_hp_event.on()

    def off(self):
        if self.__active == 0:
            return
        self.logwrapper(
            self.name,
            f"{self.mod_type}({self.mod_order}): {self.value():.02f}",
            f"buff end <turn off>",
        )
        self.__active = 0
        self.buff_end_timer.off()
        self.effect_off()
        return self

    @property
    def adv(self):
        return self._static.adv

    @allow_acl
    def timeleft(self):
        return -1 if self.duration == -1 else (self.buff_end_timer.timing -
                                               now())

    def add_time(self, delta):
        self.buff_end_timer.add(delta)

    def pause(self):
        self.pause_time = self.timeleft()
        if self.pause_time > 0:
            log("pause", self.name, self.pause_time)
            self.buff_end_timer.off()

    def resume(self):
        self.pause_time = max(self.pause_time, self.refresh_time)
        if self.pause_time > 0:
            log("resume", self.name, self.pause_time, now() + self.pause_time)
            self.buff_end_timer.on(self.pause_time)
        self.pause_time = -1