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