Exemple #1
0
 def __init__( self, self_camp, enemy_camp, max_empty_rounds, max_rounds, on_update, **kargs ) :
     super( UICombat, self ).__init__( **kargs )
     self.bind( on_open= UICombat.on_opened,
                on_dismiss= UICombat.on_dismissing )
     self.combat= Combat()
     self.combat.create_character= self.create_character
     self.self_camp= self_camp
     self.enemy_camp= enemy_camp
     self.active_character= None
     self.skill_id_casting= 0
     self.__max_empty_rounds= max_empty_rounds
     self.__max_rounds= max_rounds
     on_update_callback= getattr( datas.story, on_update, None )
     if hasattr( on_update_callback, u'__call__' ) :
         self.__on_update= on_update_callback( env, self.combat )
         self.__on_update.send( None )
     else :
         self.__on_update= None
     self.__running= False
     self.__ended= False
     self.__character_on_press_queue= []
     self.__valid_targets= []
     self.__log_queue= []
     self.__last_log_parse= 0
     self.__float_text_atlas= Atlas( 0, 0, 0, 0 )
     self.set_character_press_callback( u'skill_target', _skill_target_select )
     Clock.schedule_interval( self.on_check_log_queue, 0 )
     self.sync()
Exemple #2
0
class UICombat( ModalView ) :
    character_callback_level_name2index= {
        u'skill_target' : 0,
    }

    pause= BooleanProperty( False )
    light_name= StringProperty()
    light_value= NumericProperty()
    light_max= NumericProperty()
    dark_name= StringProperty()
    dark_value= NumericProperty()
    dark_max= NumericProperty()
    chaos_name= StringProperty()
    chaos_value= NumericProperty()
    chaos_max= NumericProperty()
    self_camp= NumericProperty()
    selected_skill_name= StringProperty()
    selected_detail= ObjectProperty()
    round_index= NumericProperty()
    acting= BooleanProperty( False )
    image= StringProperty()
    log_text= StringProperty()

    character_list_scrollview= ObjectProperty()
    character_list_scrollview_content= ObjectProperty()
    self_character_list= ObjectProperty()
    enemy_character_list= ObjectProperty()
    ui_skills= ObjectProperty()

    def __init__( self, self_camp, enemy_camp, max_empty_rounds, max_rounds, on_update, **kargs ) :
        super( UICombat, self ).__init__( **kargs )
        self.bind( on_open= UICombat.on_opened,
                   on_dismiss= UICombat.on_dismissing )
        self.combat= Combat()
        self.combat.create_character= self.create_character
        self.self_camp= self_camp
        self.enemy_camp= enemy_camp
        self.active_character= None
        self.skill_id_casting= 0
        self.__max_empty_rounds= max_empty_rounds
        self.__max_rounds= max_rounds
        on_update_callback= getattr( datas.story, on_update, None )
        if hasattr( on_update_callback, u'__call__' ) :
            self.__on_update= on_update_callback( env, self.combat )
            self.__on_update.send( None )
        else :
            self.__on_update= None
        self.__running= False
        self.__ended= False
        self.__character_on_press_queue= []
        self.__valid_targets= []
        self.__log_queue= []
        self.__last_log_parse= 0
        self.__float_text_atlas= Atlas( 0, 0, 0, 0 )
        self.set_character_press_callback( u'skill_target', _skill_target_select )
        Clock.schedule_interval( self.on_check_log_queue, 0 )
        self.sync()

    def on_opened( self ) :
        self.begin()

    def on_dismissing( self ) :
        if not self.__ended :
            popup= Factory.HintCancelCombat()
            def confirm() :
                popup.dismiss()
                self.__ended= True
                self.dismiss()
            popup.confirm= confirm
            popup.open()
            return True

    def create_character( self, data, camp =None ) :
        character= Character( game.obj_id_generator.generate() )
        self.combat.objects.append( character )
        init_character( character )
        data2character( character, data )
        if camp is not None :
            character.camp= camp
        if character.camp == self.self_camp and not character.auto_control :
            character.controller= CommandController()
        else :
            character.controller= AIController()
        return character

    def __sync_characters( self, ui_character_list, camp ) :
        characters= [ character for character in self.combat.objects if character.camp == camp ]
        characters.sort()
        len_need= len( characters )
        def create_ui_character() :
            ui_character= Factory.UICharacter()
            ui_character.ui_combat= self
            return ui_character
        resize( ui_character_list, len_need, create_ui_character )
        assert len( ui_character_list.children ) == len_need
        for i, character in enumerate( characters ) :
            ui_character= ui_character_list.children[i]
            ui_character.character= character

            ui_character.name= character.name
            ui_character.image= character.image
            ui_character.seq= str( 1+self.combat.objects.index( character ) )

            ui_character.hp_name= get_property( u'hp' ).__doc__
            ui_character.hp_value= character.hp
            ui_character.hp_max= character.hp_max

            ui_character.speed_name= get_property( u'speed' ).__doc__
            ui_character.speed_value= character.speed

            ui_character.dex_name= get_property( u'dex' ).__doc__
            ui_character.dex_value= character.dex

            ui_character.is_active= ( character is self.active_character )
            ui_character.is_invalid_target= ( self.selected_detail.current == u'skill' and \
                                              character not in self.__valid_targets )

            states= [ state for state in character.iter_states() ]
            len_need= len( states )
            resize( ui_character.state_list, len_need, Factory.UIState )
            assert( len( ui_character.state_list.children ) == len_need )
            for j, state in enumerate( states ) :
                ui_state= ui_character.state_list.children[j]
                ui_state.icon= state.icon

    def sync( self ) :
        self.sync_selected_skill_info()
        show_empty= ( self.active_character is None or \
                      self.active_character.camp != self.self_camp or \
                      not isinstance( self.active_character.controller, CommandController ) or \
                      self.active_character.controller.actions )
        if self.selected_detail.current == u'empty' and not show_empty :
            self.selected_detail.current= u'skills'
        elif self.selected_detail.current != u'empty' and show_empty :
            self.selected_detail.current= u'empty'
        if self.selected_detail.current == u'skills' :
            skill_src= self.active_character
            skills= [ ( skill_id, skill_src.get_skill( skill_id ) ) for skill_id in skill_src.skills_using ]
            skills= filter( lambda ( skill_id, skill ) : skill is not None, skills )
            skills.sort( reverse= True, key= lambda ( skill_id, skill ) : skill_id )
            len_need= len( skills )
            def create_ui_skill_menu_item() :
                ui_skill_menu_item= Factory.UISkillMenuItem()
                ui_skill_menu_item.ui_combat= self
                ui_skill_menu_item.skill_src= None
                ui_skill_menu_item.skill_id= None
                return ui_skill_menu_item
            resize( self.ui_skills, len_need, create_ui_skill_menu_item )
            for i in xrange( len_need ) :
                skill_id, skill= skills[i]
                ui_skill_item= self.ui_skills.children[i]
                ui_skill_item.skill_name= skill.name
                ui_skill_item.skill_src= skill_src
                ui_skill_item.skill_id= skill_id
                ui_skill_item.is_valid= skill.valid( self.combat, self.combat.objects, skill_src )
                ui_skill_item.icon= skill.icon
        self.__update_valid_ui_targets()
        self.__sync_characters( self.self_character_list,  self.self_camp  )
        self.__sync_characters( self.enemy_character_list, self.enemy_camp )
        self.light_name= get_property( u'light' ).__doc__
        self.light_value= self.combat.light
        self.light_max= self.combat.light_max
        self.dark_name= get_property( u'dark' ).__doc__
        self.dark_value= self.combat.dark
        self.dark_max= self.combat.dark_max
        self.chaos_name= get_property( u'chaos' ).__doc__
        self.chaos_value= self.combat.chaos
        self.chaos_max= self.combat.chaos_max

    def is_casting_skill( self ) :
        if self.active_character is None :
            return False
        if self.selected_detail.current != u'skill' :
            return False
        skill_caster= self.active_character
        skill= skill_caster.get_skill( self.skill_id_casting )
        if skill is None :
            return False
        return True

    def __update_valid_ui_targets( self ) :
        del self.__valid_targets[:]
        if not self.is_casting_skill() :
            return
        skill_caster= self.active_character
        skill= skill_caster.get_skill( self.skill_id_casting )
        assert skill is not None
        self.__valid_targets[:]= valid_targets4skill( skill,
                                                      skill_caster,
                                                      self.combat,
                                                      self.combat.objects )

    def ui_by_character( self, character ) :
        for ui_character in self.self_character_list.children :
            if ui_character.character is character :
                return ui_character
        for ui_character in self.enemy_character_list.children :
            if ui_character.character is character :
                return ui_character

    def ui_owner_by_name( self, name ) :
        for ui_character in self.self_character_list.children :
            if ui_character.name == name :
                return ui_character
        for ui_character in self.enemy_character_list.children :
            if ui_character.name == name :
                return ui_character
        if self.combat.name == name :
            return self

    def __parse_logs( self, name2text_timelines, logs ) :
        for log in logs :
            if isinstance( log, LogSkillCast ) :
                pattern= u'对 {dst} 使用了 {skill_name}'
                text= pattern.format( dst= log.dst_name,
                                      skill_name= log.skill_name )
                full_pattern= u'{src} 对 {dst} 使用了 {skill_name}'
                full_text= full_pattern.format( src= log.src_name,
                                                dst= log.dst_name,
                                                skill_name= log.skill_name )
                name2text_timelines[log.src_name].append( ( text, full_text, datas.ui_effects.timelines_float_text ) )
                self.__parse_logs( name2text_timelines, log.logs )
            elif isinstance( log, LogPropChanged ) :
                pattern= u'{prop} {value_delta:+}'
                text= pattern.format( prop= log.prop_name,
                                      value_delta= log.value_delta )
                full_pattern= u'{owner} 的 {prop} {value_delta:+}'
                full_text= full_pattern.format( owner= log.owner_name,
                                                prop= log.prop_name,
                                                value_delta= log.value_delta )
                if log.prop_name == get_property( u'hp' ).__doc__ :
                    if log.value_delta <= 0 :
                        timeline= datas.ui_effects.timelines_harm
                    else :
                        timeline= datas.ui_effects.timelines_heal
                else :
                    timeline= datas.ui_effects.timelines_float_text
                name2text_timelines[log.owner_name].append( ( text, full_text, timeline ) )
            elif isinstance( log, LogState ) :
                if log.logs :
                    pattern= u'{state_name} 生效'
                    text= pattern.format( state_name= log.state_name )
                    full_pattern= u'{owner} 的 {state_name} 生效'
                    full_text= full_pattern.format( owner= log.owner_name,
                                                    state_name= log.state_name )
                    name2text_timelines[log.owner_name].append( ( text, full_text, datas.ui_effects.timelines_float_text ) )
                    self.__parse_logs( name2text_timelines, log.logs )
            elif isinstance( log, LogStateEnter ) :
                pattern= u'获得了 {state_name} 状态'
                text= pattern.format( state_name= log.state_name )
                full_pattern= u'{owner} 获得了 {state_name} 状态'
                full_text= full_pattern.format( owner= log.owner_name,
                                                state_name= log.state_name )
                name2text_timelines[log.owner_name].append( ( text, full_text, datas.ui_effects.timelines_float_text ) )
            elif isinstance( log, LogStateLeave ) :
                pattern= u'失去了 {state_name} 状态'
                text= pattern.format( state_name= log.state_name )
                full_pattern= u'{owner} 失去了 {state_name} 状态'
                full_text= full_pattern.format( owner= log.owner_name,
                                                state_name= log.state_name )
                name2text_timelines[log.owner_name].append( ( text, full_text, datas.ui_effects.timelines_float_text ) )
            elif isinstance( log, LogEvent ) :
                name2text_timelines[self.combat.name].append( ( log.text, log.text, log.timeline ) )
            elif isinstance( log, LogEffect ) :
                ui_owner= self.ui_owner_by_name( log.owner_name )
                if ui_owner is not None :
                    if isinstance( ui_owner, Factory.UICharacter ) :
                        ui_owner= ui_owner.ids.image_widget
                    timelines= timelines_effects.get( log.effect_name, None )
                    if timelines :
                        play_effect( ui_owner.canvas.after, ui_owner.center, timelines )
            else :
                Logger.error( u'unknown log: {name}'.format( name= log.__cls__.name ) )

    def parse_logs( self, logs ) :
        self.__last_log_parse= Clock.get_boottime()
        name2text_timelines= defaultdict( list ) # name -> [ ( text, timeline ), ]
        self.__parse_logs( name2text_timelines, logs )
        for name, text_timelines in name2text_timelines.iteritems() :
            ui_owner= self.ui_owner_by_name( name )
            if ui_owner is not None :
                self.__float_text_atlas.clear()
                if ui_owner is self :
                    self.__float_text_atlas.width= 0
                    self.__float_text_atlas.height= 99999
                else :
                    self.__float_text_atlas.width= metrics.dp( 150 )
                    self.__float_text_atlas.height= 99999
                if ui_owner is self :
                    pos= ( ui_owner.center[0], ui_owner.center[1] )
                    halign= u'center'
                elif ui_owner.character.camp == self.self_camp :
                    pos= ( ui_owner.right, ui_owner.y )
                    halign= u'left'
                elif ui_owner.character.camp != self.self_camp :
                    pos= ( ui_owner.x, ui_owner.y )
                    halign= u'right'
                else :
                    continue
                for text, full_text, timelines in reversed( text_timelines ) :
                    float_text_atlas( self.__float_text_atlas, ui_owner.canvas.after, text, pos, timelines, halign )
                    self.log_text += full_text
                    self.log_text += '\n'
        self.__float_text_atlas.clear()

    def is_time_to_parse_logs( self ) :
        return self.__last_log_parse <= 0 or \
               Clock.get_boottime() - self.__last_log_parse >= log_parse_interval

    def on_check_log_queue( self, dt ) :
        if not self.__log_queue :
            return
        if self.is_time_to_parse_logs() :
            logs= self.__log_queue.pop( 0 )
            self.parse_logs( logs )

    def queued_parse_logs( self, logs ) :
        if self.__log_queue or not self.is_time_to_parse_logs() :
            self.__log_queue.append( logs )
        else :
            self.parse_logs( logs )

    def show_character( self, character ) :
        ui_character= self.ui_by_character( character )
        if ui_character is None :
            return
        view_height= self.character_list_scrollview.height
        scroll_range= self.character_list_scrollview_content.height - view_height
        if scroll_range <= 0 :
            return
        scroll_y= ( ui_character.center[1] - view_height/2 ) / scroll_range
        scroll_y= min( max( 0, scroll_y ), 1 )
        Animation( scroll_y= scroll_y, duration= .5, t= u'in_out_cubic' ).start( self.character_list_scrollview )

    def __update( self ) :
        if self.__on_update :
            finished= self.__on_update.send( None )
            if finished :
                self.__ended= True
                return True
    def __run( self ) :
        combat= self.combat
        self.queued_parse_logs( ( LogEvent( u'战斗开始', datas.ui_effects.timelines_float_text ), ) )
        yield .5
        log_combat= LogBlock()
        with combat.one_combat() as ( log_combat.logs_enter, log_combat.logs_leave ) :
            self.queued_parse_logs( log_combat.logs_enter )
            empty_rounds= 0
            for i in xrange( self.__max_rounds ) :
                self.round_index= i
                self.queued_parse_logs( ( LogEvent( u'第{i}轮'.format( i= i+1 ), datas.ui_effects.timelines_float_text ), ) )
                yield 1
                log_round= LogBlock()
                has_action= False
                with combat.one_round() as ( log_round.logs_enter, log_round.logs_leave ) :
                    self.queued_parse_logs( log_round.logs_enter )
                    for obj in combat.objects :
                        self.active_character= obj
                        self.show_character( obj )
                        self.queued_parse_logs( ( LogEvent( u'{name}的回合'.format( name= obj.name ), datas.ui_effects.timelines_float_text ), ) )
                        yield 1
                        log_turn= LogBlock()
                        with combat.one_turn( obj ) as ( log_turn.logs_enter, log_turn.logs_leave ) :
                            self.queued_parse_logs( log_turn.logs_enter )
                            if obj.controller :
                                for j in xrange( 3 ) :
                                    if j > 0 :
                                        self.queued_parse_logs( ( LogEvent( u'{name} 连击'.format( name= obj.name ), datas.ui_effects.timelines_combo ), ) )
                                    del log_turn.logs[:]
                                    yield 0
                                    if isinstance( obj.controller, CommandController ) and not obj.controller.actions :
                                        self.pause= True
                                        yield 0
                                    action= obj.controller.get_action( obj, combat, combat.objects )
                                    if action :
                                        self.acting= True
                                        exec_action( log_turn.logs,
                                                     action,
                                                     obj,
                                                     combat,
                                                     combat.objects )
                                        has_action= True
                                        self.sync()
                                        self.queued_parse_logs( log_turn.logs )
                                        yield 3
                                        self.acting= False
                                    if self.__update() :
                                        return
                                    combo_chance= obj.dex / 100.
                                    if not random() < combo_chance :
                                        skill= action_get_skill( action, obj )
                                        if not skill or not random() < skill.combo_chance :
                                            break
                        self.queued_parse_logs( log_turn.logs_leave )
                        self.active_character= None
                        yield 1
                combat_prop_change( log_round.logs_leave, combat, u'light', u'dlight_min', u'dlight_max' )
                combat_prop_change( log_round.logs_leave, combat, u'dark',  u'ddark_min',  u'ddark_max'  )
                combat_prop_change( log_round.logs_leave, combat, u'chaos', u'dchaos_min', u'dchaos_max' )
                self.queued_parse_logs( log_round.logs_leave )
                self.round_index= -1
                if has_action :
                    empty_rounds= 0
                else :
                    empty_rounds += 1
                    if self.__max_empty_rounds > 0 and empty_rounds >= self.__max_empty_rounds :
                        break
                yield 1
        self.queued_parse_logs( log_combat.logs_leave )
        self.queued_parse_logs( ( LogEvent( u'战斗结束', datas.ui_effects.timelines_float_text ), ) )
        self.__ended= True

    def begin( self ) :
        if self.__running :
            return
        self.__running= True
        combat_routine= self.__run()
        def on_step( dt ) :
            if self.__ended :
                return
            if self.pause or self.__log_queue or not self.is_time_to_parse_logs() :
                Clock.schedule_once( on_step, .1 )
            else :
                try :
                    time2sleep= combat_routine.next()
                    time2sleep= max( time2sleep, 0 )
                    Clock.schedule_once( on_step, time2sleep )
                except StopIteration, e:
                    Clock.unschedule( on_step )
                    assert self.__ended
                    def on_exit( dt ) :
                        def exit_combat() :
                            self.dismiss()
                            try :
                                self.__on_update.send( None )
                            except StopIteration, e:
                                return
                        Factory.CombatFinishedFade( on_stay= exit_combat ).open()
                    Clock.schedule_once( on_exit, 1 )
            self.sync()