NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

conversationcontroller.py (16166B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\conversations\conversationcontroller.py
      2 import localization
      3 from conversations.const import START_CONVERSATION_EVENT, END_CONVERSATION_EVENT, MINIMIZED_CONVERSATION_EVENT, MAXIMIZED_CONVERSATION_EVENT
      4 from conversations.conversation import ConversationAgent, LineHintImage
      5 from conversations.fsdloaders import ConversationAgentsLoader
      6 from conversations.ui.instructionsconversationwindow import InstructionsConversationWindow, InstructionsConversationWindowWithCloseButton
      7 from fsdBuiltData.common.iconIDs import GetIcon
      8 from fsdBuiltData.common.soundIDs import GetSoundEventName
      9 from log import LogWarn, LogException
     10 import uthread2
     11 
     12 class ConversationController(object):
     13 
     14     def __init__(self, conversation_manager):
     15         self._conversation_manager = conversation_manager
     16         self._conversation_window = None
     17         self.active_conversation = None
     18         self._are_air_npe_agents_preloaded = False
     19         self._agents_preloaded = set()
     20         self.opening_thread = None
     21         self.opening_thread_for_convo_id = None
     22         self._clear_active_conversation_data()
     23 
     24     def show_conversation(self, conversation_id, show_warning_on_first_close_setting = True, show_continue_button = False, should_close_on_continue = False, should_autoprogress = True, variables = None, show_only_once = False):
     25         if self.opening_thread:
     26             if self.opening_thread_for_convo_id == conversation_id:
     27                 return
     28             self.opening_thread.kill()
     29             self.opening_thread = None
     30             self.opening_thread_for_convo_id = None
     31             self.processing_show_conversation_id = None
     32         self.opening_thread = uthread2.start_tasklet(self._show_conversation, conversation_getter=self._conversation_manager.get_conversation, conversation_id=conversation_id, show_continue_button=show_continue_button, error_message='Failed to display conversation (id: %s)' % conversation_id, show_warning_on_first_close_setting=show_warning_on_first_close_setting, should_close_on_continue=should_close_on_continue, should_autoprogress=should_autoprogress, variables=variables, show_only_once=show_only_once)
     33         self.opening_thread_for_convo_id = conversation_id
     34 
     35     def _show_conversation(self, conversation_getter, conversation_id, error_message, show_continue_button = False, show_warning_on_first_close_setting = True, should_close_on_continue = False, should_autoprogress = True, variables = None, show_only_once = False):
     36         uthread2.sleep(0.1)
     37         is_processing_it = self.processing_show_conversation_id == conversation_id
     38         is_already_active = self.active_conversation is not None and self.active_conversation.id == conversation_id
     39         if is_already_active and self._conversation_window and not self._conversation_window.destroyed:
     40             self._conversation_window.Maximize()
     41         if is_already_active or is_processing_it:
     42             self.opening_thread = None
     43             self.opening_thread_for_convo_id = None
     44             return
     45         try:
     46             conversation = conversation_getter(conversation_id, variables)
     47             self.processing_show_conversation_id = conversation_id
     48             self.force_hiding_active_conversation()
     49             if show_only_once:
     50                 only_once_conversations = settings.char.ui.Get('only_once_conversations', set())
     51                 if conversation_id in only_once_conversations:
     52                     if show_continue_button:
     53                         sm.ScatterEvent('OnClientEvent_ConversationContinueButtonClicked', conversation_id)
     54                     self.opening_thread = None
     55                     self.opening_thread_for_convo_id = None
     56                     self.processing_show_conversation_id = None
     57                     return
     58                 only_once_conversations.add(conversation_id)
     59                 settings.char.ui.Set('only_once_conversations', only_once_conversations)
     60             self.active_conversation = conversation
     61             self.active_conversation_lines = list(self.active_conversation)
     62             self.active_conversation_line_index = None
     63             self._show_conversation_window(show_continue_button, show_warning_on_first_close_setting, should_close_on_continue, should_autoprogress)
     64             self.opening_thread = None
     65             self.opening_thread_for_convo_id = None
     66         except (ValueError, RuntimeError) as exc:
     67             self.force_hiding_active_conversation()
     68             LogException('{error}: {exc}'.format(error=error_message, exc=exc))
     69             self.opening_thread = None
     70             self.opening_thread_for_convo_id = None
     71             raise ValueError(error_message)
     72 
     73     def force_hiding_active_conversation(self):
     74         if self._conversation_window:
     75             if not self._conversation_window.destroyed:
     76                 self._conversation_window.close_window()
     77             self._conversation_window = None
     78             self._clear_active_conversation_data()
     79 
     80     def hide_conversation(self):
     81         if not self.is_conversation_window_available():
     82             return
     83         self.close_conversation()
     84 
     85     def close_conversation(self):
     86         self._conversation_window.Close()
     87 
     88     def is_conversation_active(self):
     89         if self.active_conversation is None:
     90             return False
     91         if self.processing_show_conversation_id is not None:
     92             return False
     93         return self._conversation_window and not self._conversation_window.destroyed and not self._conversation_window.IsMinimized()
     94 
     95     def is_processing_show_conversation(self):
     96         return self.processing_show_conversation_id is not None
     97 
     98     def clear_conversation(self, conversation_id):
     99         active_conversation_id = self.get_active_conversation_id()
    100         if active_conversation_id is None or active_conversation_id != conversation_id:
    101             return
    102         self.clear_active_conversation()
    103 
    104     def clear_active_conversation(self):
    105         self.processing_show_conversation_id = None
    106         self._clear_active_conversation_data()
    107 
    108     def _clear_active_conversation_data(self):
    109         if self.active_conversation is not None:
    110             self._notify_of_conversation_end()
    111         self.active_conversation = None
    112         self.active_conversation_lines = None
    113         self.active_conversation_line_index = None
    114         self.processing_show_conversation_id = None
    115 
    116     def get_conversation_window(self):
    117         return self._conversation_window
    118 
    119     def is_conversation_window_available(self):
    120         return self._conversation_window and not self._conversation_window.destroyed
    121 
    122     def show_completed_ui(self):
    123         self._conversation_window.show_completed_ui()
    124 
    125     def notify_of_conversation_start(self, conversation_id):
    126         active_conversation_id = self.get_active_conversation_id()
    127         if active_conversation_id is None or active_conversation_id != conversation_id:
    128             return
    129         self.processing_show_conversation_id = None
    130         self._notify_of_conversation_event(START_CONVERSATION_EVENT)
    131 
    132     def _notify_of_conversation_end(self):
    133         self._notify_of_conversation_event(END_CONVERSATION_EVENT)
    134 
    135     def notify_of_conversation_minimized(self, conversation_id):
    136         active_conversation_id = self.get_active_conversation_id()
    137         if active_conversation_id is None or active_conversation_id != conversation_id:
    138             return
    139         self._notify_of_conversation_event(MINIMIZED_CONVERSATION_EVENT)
    140 
    141     def notify_of_conversation_maximized(self, conversation_id):
    142         active_conversation_id = self.get_active_conversation_id()
    143         if active_conversation_id is None or active_conversation_id != conversation_id:
    144             return
    145         self._notify_of_conversation_event(MAXIMIZED_CONVERSATION_EVENT)
    146 
    147     def _notify_of_conversation_event(self, conversation_event):
    148         if not self.active_conversation:
    149             return
    150         conv_event = conversation_event
    151         conv_id = self.active_conversation.id
    152         sm.ScatterEvent(conv_event, conv_id)
    153 
    154     def get_active_conversation_id(self):
    155         if self.active_conversation is None:
    156             return
    157         return self.get_conversation_id(self.active_conversation)
    158 
    159     def get_conversation_id(self, conversation):
    160         return conversation.id
    161 
    162     def _show_conversation_window(self, show_continue_button = False, show_warning_on_first_close_setting = True, should_close_on_continue = False, should_autoprogress = True):
    163         if self.active_conversation is None or not self.are_any_conversation_lines_left():
    164             return
    165         conversation_id = self.get_active_conversation_id()
    166         if conversation_id is not None:
    167             if show_continue_button:
    168                 conversation_window_class = InstructionsConversationWindowWithCloseButton
    169             else:
    170                 conversation_window_class = InstructionsConversationWindow
    171             window_name = self._get_conversation_window_name(conversation_window_class)
    172             self._conversation_window = conversation_window = conversation_window_class(conversation_id=conversation_id, is_completed=False, show_warning_on_first_close_setting=show_warning_on_first_close_setting, should_close_on_continue=should_close_on_continue, should_autoprogress=should_autoprogress)
    173             conversation_window.name = window_name
    174             if conversation_id != self.processing_show_conversation_id:
    175                 conversation_window.Close()
    176 
    177     def _get_conversation_window_name(self, conversation_window_class):
    178         if conversation_window_class in [InstructionsConversationWindow, InstructionsConversationWindowWithCloseButton]:
    179             return 'TaskConversationWindow'
    180         return 'ConversationWindow'
    181 
    182     def are_any_conversation_lines_left(self):
    183         if self.active_conversation is None:
    184             return False
    185         elif self.active_conversation_line_index is None:
    186             return len(self.active_conversation) > 0
    187         else:
    188             return self.active_conversation_line_index + 1 < len(self.active_conversation_lines)
    189 
    190     def get_next_conversation_line(self):
    191         self.processing_show_conversation_id = self.active_conversation.id
    192         if not self.are_any_conversation_lines_left():
    193             LogException('Failed to find more lines in the active conversation')
    194             self.force_hiding_active_conversation()
    195             return
    196         if self.active_conversation_line_index is None:
    197             self.active_conversation_line_index = 0
    198         else:
    199             self.active_conversation_line_index += 1
    200         return self.active_conversation_lines[self.active_conversation_line_index]
    201 
    202     def are_any_previous_conversation_lines_available(self):
    203         return self.active_conversation_line_index is not None and self.active_conversation_line_index > 0
    204 
    205     def get_previous_conversation_line(self):
    206         self.processing_show_conversation_id = self.active_conversation.id
    207         if self.active_conversation_line_index is None or self.active_conversation_line_index == 0:
    208             LogException('The active conversation is already at the beginning')
    209             return
    210         self.active_conversation_line_index -= 1
    211         return self.active_conversation_lines[self.active_conversation_line_index]
    212 
    213     def get_active_conversation_line(self):
    214         return self.active_conversation_lines[self.active_conversation_line_index]
    215 
    216     def get_agent_name(self, agent_id):
    217         return self._conversation_manager.get_agent(agent_id).name
    218 
    219     def get_agent(self, agent_id):
    220         return self._conversation_manager.get_agent(agent_id)
    221 
    222     def get_line_hint_image(self, line_hint_image_id):
    223         return self._conversation_manager.get_line_hint_image(line_hint_image_id)
    224 
    225     def get_play_event(self, sound_id):
    226         if not sound_id:
    227             return ''
    228         return self._conversation_manager.get_play_event(sound_id)
    229 
    230     def get_stop_event(self, sound_id):
    231         return self._conversation_manager.get_stop_event(sound_id)
    232 
    233     def get_music_trigger(self, music_trigger_id):
    234         return self._conversation_manager.get_music_trigger(music_trigger_id)
    235 
    236     def preload_air_npe_agents(self):
    237         if not self._are_air_npe_agents_preloaded:
    238             import avatardisplay.avatardisplay as ad
    239             self._are_air_npe_agents_preloaded = True
    240             scenes = ['res:/Animation_Gstate/Scenes/AIR_NPE/Aura/AIR_NPE_Aura_2755_1/',
    241              'res:/Animation_Gstate/Scenes/AIR_NPE/Aura/AIR_NPE_Aura_2783_1/',
    242              'res:/Animation_Gstate/Scenes/AIR_NPE/Aura/AIR_NPE_Aura_2831_1/',
    243              'res:/Animation_Gstate/Scenes/AIR_NPE/Aura/AIR_NPE_Aura_2892_1/',
    244              'res:/Animation_Gstate/Scenes/AIR_NPE/Balin/AIR_NPE_Balin_2753_1/',
    245              'res:/Animation_Gstate/Scenes/AIR_NPE/Balin/AIR_NPE_Balin_2776_1/',
    246              'res:/Animation_Gstate/Scenes/AIR_NPE/Elias/AIR_NPE_Elias_2932_1',
    247              'res:/Animation_Gstate/Scenes/AIR_NPE/Vesper/AIR_NPE_Vesper_2811_2/']
    248             ad.PreloadScene(scenes, curve_list_yaml='res:/Animation_Gstate/Scenes/AIR_NPE/curve_cache_list.yaml')
    249 
    250     def preload_agent(self, agent_id):
    251         if agent_id in self._agents_preloaded:
    252             return
    253         try:
    254             self._agents_preloaded.add(agent_id)
    255             agent_data = self._fetch_agent_data(agent_id)
    256             if agent_data:
    257                 import avatardisplay.avatardisplay as ad
    258                 ad.PreloadScene(agent_data.scenePath, curve_list_yaml=agent_data.preloadCachePath)
    259         except Exception as exc:
    260             LogException('Unable to preload scenes: ', exc)
    261             if agent_id in self._agents_preloaded:
    262                 self._agents_preloaded.remove(agent_id)
    263 
    264     def _fetch_agent_data(self, agent_id):
    265         from conversations.fsdloaders import ConversationAgentsLoader
    266         return ConversationAgentsLoader.GetByID(agent_id)
    267 
    268 
    269 class BaseConversationManager(object):
    270 
    271     def __init__(self):
    272         self._conversation_sounds = ConversationSounds()
    273 
    274     def get_localized_video_paths(self, agent):
    275         loop_video_path = agent.loopVideoPath
    276         intro_video_path = agent.introVideoPath
    277         if localization.util.AmOnChineseServer():
    278             intro_video_path = agent.introVideoPathChinese or intro_video_path
    279             loop_video_path = agent.loopVideoPathChinese or loop_video_path
    280         return (loop_video_path, intro_video_path)
    281 
    282     def get_agent(self, agent_id):
    283         agent = ConversationAgentsLoader.GetByID(agent_id)
    284         if agent is None:
    285             raise ValueError('No agent found with id {id}'.format(id=agent_id))
    286         name = agent.name
    287         image_path = agent.imagePath
    288         loop_video_path, intro_video_path = self.get_localized_video_paths(agent)
    289         scene_path = agent.scenePath
    290         return ConversationAgent(agent_id, name, image_path, intro_video_path, loop_video_path, scene_path)
    291 
    292     def get_line_hint_image(self, line_hint_image_id):
    293         line_hint_image = GetIcon(line_hint_image_id)
    294         if line_hint_image is None:
    295             raise ValueError('No line hint image found with id {id}'.format(id=line_hint_image_id))
    296         description = getattr(line_hint_image, 'description', 'default_description')
    297         image_path = line_hint_image.iconFile
    298         return LineHintImage(line_hint_image_id, description, image_path)
    299 
    300     def get_play_event(self, sound_id):
    301         return self._conversation_sounds.get_play_event(sound_id)
    302 
    303     def get_stop_event(self, sound_id):
    304         return self._conversation_sounds.get_stop_event(sound_id)
    305 
    306     def get_conversation(self, conversation_id):
    307         raise NotImplementedError('Implement creating the corresponding conversation objects')
    308 
    309 
    310 class ConversationSounds(object):
    311 
    312     def __init__(self):
    313         self.sounds = None
    314 
    315     def get_play_event(self, sound_id):
    316         play_event = GetSoundEventName(sound_id)
    317         if play_event is not None:
    318             return play_event
    319 
    320     def get_stop_event(self, sound_id):
    321         play_event = self.get_play_event(sound_id)
    322         if play_event is not None:
    323             return play_event.replace('_play', '_stop')