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()
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()