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

conversationwindow.py (52894B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\conversations\ui\conversationwindow.py
      2 from math import pi
      3 import time
      4 import blue
      5 import carbonui.const as uiconst
      6 import uthread
      7 from carbonui.control.scrollContainer import ScrollContainer
      8 from carbonui.fontconst import DEFAULT_FONTSIZE
      9 from carbonui.primitives.container import Container
     10 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     11 from carbonui.primitives.sprite import Sprite, StreamingVideoSprite
     12 from carbonui.uianimations import animations
     13 from carbonui.uicore import uicore
     14 from carbonui.util.color import Color
     15 from conversations.animatedtransmissionwindow import AnimatedTransmissionWindow
     16 from conversations.const import CONVERSATION_WINDOW_ID
     17 from conversations.ui.completedconversationui import CompletedConversationUi
     18 from conversations.ui.conversationwindowstyle import ConversationWindowStyle, AGENT_ID_TO_STYLE
     19 from conversations.ui.conversationwindowunderlay import ConversationWindowUnderlay
     20 from carbonui.control.button import Button
     21 from eve.client.script.ui.control.eveLabel import EveLabelLarge
     22 from eve.client.script.ui.control.themeColored import SpriteThemeColored, LineThemeColored
     23 from eve.client.script.ui.shared.pointerTool.pointerToolConst import UNIQUE_NAME_NEOCOM
     24 from eve.client.script.ui.util.uix import GetTextWidth
     25 from globalConfig import GetConversationLine3dPortraitEnabled
     26 from localization import GetByMessageID, GetByLabel, IsPrimaryLanguage
     27 from player_messaging.client.ui_message import prompt_player_fsd_dialog
     28 from trinity import TR2_SBM_ADD, TR2_SBM_NONE
     29 from uihider import get_ui_hider, UiHiderMixin
     30 from uthread2 import call_after_wallclocktime_delay
     31 import logging
     32 logger = logging.getLogger(__name__)
     33 COLOR_LABEL = Color.HextoRGBA('#e5f1ff')
     34 DEFAULT_WIDTH = 580
     35 DEFAULT_HEIGHT = 640
     36 MAIN_CONTAINER_PADDING_WINDOW = 0
     37 MAIN_CONTAINER_PADDING_TOP = 0
     38 MAIN_CONTAINER_PADDING = (MAIN_CONTAINER_PADDING_WINDOW,
     39  MAIN_CONTAINER_PADDING_WINDOW + MAIN_CONTAINER_PADDING_TOP,
     40  MAIN_CONTAINER_PADDING_WINDOW,
     41  MAIN_CONTAINER_PADDING_WINDOW)
     42 CONTENT_WIDTH = DEFAULT_WIDTH - 2 * MAIN_CONTAINER_PADDING_WINDOW
     43 CONTENT_HORIZONTAL_PADDING = 20
     44 CONTENT_VERTICAL_PADDING = 12
     45 AGENT_PORTRAIT_SIZE = 320
     46 AGENT_PORTRAIT_VERTICAL_PADDING = 1
     47 AGENT_CONTAINER_GRADIENT = 'res:/UI/Texture/classes/Seasons/lineBreakGradient.png'
     48 AGENT_CONTAINER_GRADIENT_OPACITY = 1.0
     49 AGENT_CONTAINER_SIDE_TEXTURE = 'res:/UI/Texture/classes/ConversationUI/sideDecoration.png'
     50 AGENT_CONTAINER_SIDE_GLOW_TEXTURE = 'res:/UI/Texture/classes/ConversationUI/sideDecorationGlow.png'
     51 AGENT_CONTAINER_SIDE_TEXTURE_WIDTH = 3
     52 AGENT_CONTAINER_SIDE_TEXTURE_HEIGHT = 36
     53 AGENT_CONTAINER_SIDE_GLOW_TEXTURE_WIDTH = 23
     54 AGENT_CONTAINER_SIDE_GLOW_TEXTURE_HEIGHT = 56
     55 AGENT_CONTAINER_SIDE_OPACITY = 0.8
     56 LINE_HEIGHT = 1
     57 TITLE_LINE_PADDING = 16
     58 TITLE_LINE_OPACITY = 0.3
     59 TITLE_CONTAINER_BOT_PADDING = 4
     60 TITLE_OPACITY = 1.0
     61 TITLE_TO_EXPAND_OPTION_PADDING = 4
     62 EXPAND_OPTION_ICON_SIZE = 12
     63 TEXT_TOP_PADDING = 16
     64 TEXT_BOTTOM_PADDING = 6
     65 TEXT_OPACITY = 1.0
     66 HINTS_CONTAINER_TOP_PADDING = 16
     67 HINTS_TO_BUTTONS_PADDING = 16
     68 HINT_CELL_PADDING = 2
     69 HINTS_COLUMN_NUMBER = 2
     70 HINT_TEXT_OPACITY = 1.0
     71 HINT_TEXT_SEPARATION = 4
     72 NEXT_CONVERSATION_LINE_LABEL = 'UI/Conversations/UI/NextConversationLine'
     73 PREVIOUS_CONVERSATION_LINE_LABEL = 'UI/Conversations/UI/PreviousConversationLine'
     74 CONTINUE_BUTTON_LABEL = 'UI/Conversations/UI/Close'
     75 NAVIGATION_BUTTON_LABEL_PADDING = 16
     76 NAVIGATION_BUTTON_HEIGHT = 24
     77 NAVIGATION_BUTTON_PADDING_TOP = 4
     78 NAVIGATION_BUTTON_PADDING_BOTTOM = 4
     79 MAIN_CONTAINER_MIN_HEIGHT = AGENT_PORTRAIT_SIZE + 2 * AGENT_PORTRAIT_VERTICAL_PADDING
     80 AUDIO_ACTIVATION_DELAY_SECONDS = 1.0
     81 ICON_TEXT_EXPAND = 'res:/UI/Texture/classes/ConversationUI/iconTextExpand.png'
     82 ICON_TEXT_COLLAPSE = 'res:/UI/Texture/classes/ConversationUI/iconTextCollapse.png'
     83 WINDOW_TITLE_LABEL = 'UI/Conversations/UI/ConversationWindowTitle'
     84 SETTING_CONVERSATION_WINDOW_TEXT_EXPAND = 'conversation_window_text_expand'
     85 COMPLETED_UI_HEIGHT = 26
     86 COMPLETED_UI_PADDING = 8
     87 MAX_TEXT_SCROLL_HEIGHT = 400
     88 DEFAULT_HINT_ICON_SIZE = 32
     89 
     90 def get_total_container_height(container):
     91     return container.height + container.padTop + container.padBottom
     92 
     93 
     94 def is_line_3d_portrait_enabled():
     95     return GetConversationLine3dPortraitEnabled(sm.GetService('machoNet'))
     96 
     97 
     98 class ConversationWindow(AnimatedTransmissionWindow, UiHiderMixin):
     99     __notifyevents__ = ['OnAudioActivated',
    100      'OnAudioDeactivated',
    101      'OnUIScalingChange',
    102      'OnHideUI',
    103      'OnShowUI',
    104      'OnSetDevice']
    105     default_windowID = CONVERSATION_WINDOW_ID
    106     default_iconNum = 'res:/UI/Texture/WindowIcons/tutorial.png'
    107     default_isCollapseable = False
    108     default_isStackable = False
    109     default_isLightBackgroundConfigurable = False
    110     default_isLockable = False
    111     default_isOverlayable = False
    112     default_isMinimizable = True
    113     default_fixedWidth = DEFAULT_WIDTH
    114     default_fixedHeight = DEFAULT_HEIGHT
    115     default_left = 385
    116     default_top = 75
    117     default_hideHeaderIcons = False
    118     default_is_content_expandable = False
    119     default_is_decoration_on_sides = True
    120     default_are_hints_visible = True
    121     default_are_navigation_buttons_enabled = True
    122     default_show_warning_on_first_close_setting = None
    123     default_show_warning_on_first_close_dialog = None
    124     default_close_on_audio_end = False
    125     default_is_close_button_visible = False
    126     default_style = ConversationWindowStyle
    127     current_sound_id = None
    128     default_conversation_id = None
    129     default_is_completed = False
    130     default_delay_seconds_between_audio_ended_and_show_next = 0
    131     default_should_close_on_continue = False
    132     default_should_autoprogress = True
    133 
    134     def ApplyAttributes(self, attributes):
    135         self.is_content_expandable = attributes.Get('is_content_expandable', self.default_is_content_expandable)
    136         self.is_decoration_on_sides = attributes.Get('is_decoration_on_sides', self.default_is_decoration_on_sides)
    137         self.are_hints_visible = attributes.Get('are_hints_visible', self.default_are_hints_visible)
    138         self.are_navigation_buttons_enabled = attributes.Get('are_navigation_buttons_enabled', self.default_are_navigation_buttons_enabled)
    139         self.show_warning_on_first_close_setting = attributes.Get('show_warning_on_first_close_setting', self.default_show_warning_on_first_close_setting)
    140         self.show_warning_on_first_close_dialog = attributes.Get('show_warning_on_first_close_dialog', self.default_show_warning_on_first_close_dialog)
    141         self.close_on_audio_end = attributes.Get('close_on_audio_end', self.default_close_on_audio_end)
    142         self.is_close_button_visible = attributes.Get('is_close_button_visible', self.default_is_close_button_visible)
    143         self.delay_seconds_between_audio_ended_and_show_next = attributes.Get('delay_seconds_between_audio_ended_and_show_next', self.default_delay_seconds_between_audio_ended_and_show_next)
    144         self.conversation_id = attributes.Get('conversation_id', self.default_conversation_id)
    145         self.is_completed = attributes.Get('is_completed', self.default_is_completed)
    146         self.should_close_on_continue = attributes.Get('should_close_on_continue', self.default_should_close_on_continue)
    147         self.should_autoprogress = attributes.Get('should_autoprogress', self.default_should_autoprogress)
    148         self.style = None
    149         self.styled_backgrounds_container = None
    150         self.background_gradient_container = None
    151         self.portrait_side_line_left_container = None
    152         self.portrait_side_line_right_container = None
    153         self.portrait_side_line_glow_left_container = None
    154         self.portrait_side_line_glow_right_container = None
    155         self.window_top_line_container = None
    156         self.window_top_line_glow_container = None
    157         self.is_waiting_for_delay_to_show_next_conversation = False
    158         self.next_conversation_tasklet = None
    159         self.showing_next = False
    160         self.title_line_container = None
    161         self.title_line = None
    162         self.expand_option_container = None
    163         self.expand_option_sprite = None
    164         self.text = None
    165         self.text_container = None
    166         self.text_label = None
    167         self.hints_line = None
    168         self.hints_container = None
    169         self.navigation_button_container = None
    170         self.completed_ui_container = None
    171         self.paragraph_labels = []
    172         self.hint_labels = []
    173         self.agent_portrait_intro_3d_container = None
    174         self.agent_portrait_intro_video_container = None
    175         self.agent_portrait_image_sprite = None
    176         self.current_agent_id = None
    177         self.current_line = None
    178         self._is_portrait_loaded = False
    179         super(ConversationWindow, self).ApplyAttributes(attributes)
    180         self.MakeUnResizeable()
    181         self.SetCaption(GetByLabel(WINDOW_TITLE_LABEL))
    182         sm.RegisterNotify(self)
    183         self._conversation_svc = sm.GetService('conversationService')
    184         self._audio_svc = sm.GetService('audio')
    185         self._audio_svc.SubscribeToEventCallback(self._on_sound_event_completed)
    186         self.is_audio_activated = self._audio_svc.IsActivated()
    187         self.sound_playing = False
    188         self.sound_start_time = 0
    189         self.manual_expanded_setting = self._get_expand_setting()
    190         content_container_alignment = self._get_content_container_alignment()
    191         self.main_container = Container(name='main_conversation_container', parent=self.sr.main, align=uiconst.TOTOP, height=DEFAULT_HEIGHT, padding=MAIN_CONTAINER_PADDING)
    192         self.portrait_container = Container(name='portrait_container', parent=self.main_container, align=self._get_portrait_container_alignment(), width=self._get_portrait_container_width(), height=self._get_portrait_container_height())
    193         self.portrait_wrapper_container = None
    194         content_horizontal_padding = self._get_content_horizontal_padding()
    195         content_top_padding = self._get_content_top_padding()
    196         content_bottom_padding = self._get_content_bottom_padding()
    197         content_width = self._get_content_width() - content_horizontal_padding
    198         self.content_container = Container(name='conversation_content_container', parent=self.main_container, align=content_container_alignment, width=content_width, height=0, padding=(content_horizontal_padding,
    199          content_top_padding,
    200          0,
    201          content_bottom_padding))
    202         self._add_title()
    203         has_incoming_transmission_sound = self._transmission_sound_effect is not None
    204         self.show_next_conversation(play_incoming_transmission_sound=has_incoming_transmission_sound)
    205         self.SetParent(uicore.layer.alwaysvisible)
    206         if self.is_neocom_hidden():
    207             self.MakeUnMinimizable()
    208 
    209     def is_neocom_hidden(self):
    210         return get_ui_hider().is_ui_element_hidden(UNIQUE_NAME_NEOCOM)
    211 
    212     def OnEndMinimize_(self, *args):
    213         super(ConversationWindow, self).OnEndMinimize_(*args)
    214         self.SetParent(uicore.layer.alwaysvisible)
    215 
    216     def OnEndMaximize_(self, *args):
    217         super(ConversationWindow, self).OnEndMaximize_(*args)
    218         self.SetParent(uicore.layer.alwaysvisible)
    219 
    220     def InitializeStatesAndPosition(self, *args, **kwds):
    221         super(ConversationWindow, self).InitializeStatesAndPosition(*args, **kwds)
    222         self._correct_height(style_changed=True)
    223         self.SetParent(uicore.layer.alwaysvisible)
    224 
    225     def animate_text_portrait_in(self):
    226         timeOffset = 0.6
    227         portrait_offset = 0.5
    228         portrait_duration = 0.5
    229         for label in self.paragraph_labels:
    230             timeOffset += self._FadeInLabel(label, timeOffset, TEXT_OPACITY)
    231 
    232         if self._should_show_hints():
    233             if self.hints_line:
    234                 animations.FadeIn(self.hints_container, duration=0.6, timeOffset=timeOffset)
    235             for label in self.hint_labels:
    236                 timeOffset += self._FadeInLabel(label, timeOffset, HINT_TEXT_OPACITY)
    237 
    238         if self._should_show_navigation_buttons():
    239             self.navigation_button_container.SetState(uiconst.UI_DISABLED)
    240             animations.FadeIn(self.navigation_button_container, 1.0, duration=0.6, timeOffset=timeOffset, callback=lambda : self.navigation_button_container.SetState(uiconst.UI_NORMAL))
    241             timeOffset += 0.6
    242         blue.synchro.SleepWallclock(1000 * portrait_offset)
    243         if self.agent_portrait_intro_3d_container is not None:
    244             self.agent_portrait_intro_3d_container.avatarscenecontainer.opacity = 0.0
    245             self.agent_portrait_intro_3d_container.avatarscenecontainer.fade_in_duration = portrait_duration
    246             self.agent_portrait_intro_3d_container.avatarscenecontainer.fade_out_duration = 0.3
    247             self.agent_portrait_intro_3d_container.avatarscenecontainer.fade_out_offset = 0.0
    248         self.notify_of_open()
    249 
    250     def _FadeInLabel(self, label, timeOffset, opacity):
    251         animations.FadeIn(label, opacity, duration=0.6, timeOffset=timeOffset)
    252         animations.MorphScalar(label, 'left', 8, 0, duration=0.4, timeOffset=timeOffset)
    253         return self._GetTimeOffsetByTextLength(label.text)
    254 
    255     def _GetTimeOffsetByTextLength(self, text):
    256         numLetters = len(text)
    257         return max(1.2, numLetters / 100.0)
    258 
    259     def _clear_container(self, container):
    260         if self._is_ui_element_available(container):
    261             container.Flush()
    262             container.Close()
    263 
    264     def _is_ui_element_available(self, ui_element):
    265         return ui_element and not ui_element.destroyed
    266 
    267     def _get_content_container_alignment(self):
    268         return uiconst.TOTOP
    269 
    270     def _get_main_container_width(self):
    271         return DEFAULT_WIDTH
    272 
    273     def _get_main_container_height(self):
    274         content_container_height_with_padding = self.content_container.height + self.content_container.padTop + self.content_container.padBottom
    275         vertical_height = MAIN_CONTAINER_MIN_HEIGHT + content_container_height_with_padding
    276         return vertical_height
    277 
    278     def _get_content_width(self):
    279         return CONTENT_WIDTH
    280 
    281     def _get_content_horizontal_padding(self):
    282         return CONTENT_HORIZONTAL_PADDING
    283 
    284     def _get_content_top_padding(self):
    285         return CONTENT_VERTICAL_PADDING
    286 
    287     def _get_content_bottom_padding(self):
    288         return CONTENT_VERTICAL_PADDING
    289 
    290     def _get_portrait_container_width(self):
    291         return self._get_content_width()
    292 
    293     def _get_portrait_container_height(self):
    294         return AGENT_PORTRAIT_SIZE + 2 * AGENT_PORTRAIT_VERTICAL_PADDING
    295 
    296     def _get_portrait_container_alignment(self):
    297         return uiconst.TOTOP
    298 
    299     def _get_agent_portrait_size(self):
    300         return (AGENT_PORTRAIT_SIZE, AGENT_PORTRAIT_SIZE)
    301 
    302     def _get_text_scroll_height(self):
    303         try:
    304             return min(self.text_paragraphs_container.height, MAX_TEXT_SCROLL_HEIGHT)
    305         except AttributeError:
    306             return MAX_TEXT_SCROLL_HEIGHT
    307 
    308     def _load_portrait(self, agent_data, next_line):
    309         self._clear_container(self.portrait_wrapper_container)
    310         self.portrait_wrapper_container = Container(name='portrait_wrapper_container', parent=self.portrait_container, align=uiconst.TOTOP, width=self.portrait_container.width, height=self.portrait_container.height)
    311         if self.is_decoration_on_sides:
    312             self._load_portrait_side_lines()
    313             self._load_portrait_side_lines_glow()
    314         self._load_agent_portrait(agent_data, next_line)
    315         if next_line.scene_path is not None or agent_data.scene_path is not None:
    316             pass
    317 
    318     def _load_agent_container_bg(self):
    319         agent_portrait_width, agent_portrait_height = self._get_agent_portrait_size()
    320         self.background_gradient = Sprite(name='agent_portrait_bg', parent=self.portrait_wrapper_container, align=uiconst.CENTER, width=agent_portrait_width, height=agent_portrait_height, texturePath=AGENT_CONTAINER_GRADIENT, opacity=AGENT_CONTAINER_GRADIENT_OPACITY)
    321         self._set_sprite_color(self.background_gradient, glow=True)
    322 
    323     def _is_using_low_quality_characters(self):
    324         from avatardisplay.avatardisplay import IsUsingLowQualityCharacters
    325         return IsUsingLowQualityCharacters()
    326 
    327     def _load_agent_portrait(self, agent_data, next_line, restart_audio = False):
    328         self._load_agent_portrait_3d(agent_data, next_line, restart_audio=restart_audio)
    329         self._load_agent_portrait_videos(agent_data)
    330         self._load_agent_portrait_image(agent_data, restart_audio=restart_audio)
    331 
    332     def _load_agent_portrait_3d(self, agent_data, next_line, restart_audio = False):
    333         if self._is_using_low_quality_characters():
    334             return
    335         if self._is_portrait_loaded or next_line.scene_path is None and agent_data.scene_path is None:
    336             return
    337         from avatardisplay.avatardisplay import PlaybackScene
    338         from avatardisplay.errors import FailedToPlaybackScene
    339         agent_portrait_width, agent_portrait_height = self._get_agent_portrait_size()
    340         self.agent_portrait_intro_3d_container = Container(parent=self.portrait_wrapper_container, name='agent_portrait_intro_3d_container', align=uiconst.CENTER, width=agent_portrait_width, height=agent_portrait_height, display=True)
    341         scene_dir = None
    342         if is_line_3d_portrait_enabled() and next_line.scene_path is not None:
    343             scene_dir = next_line.scene_path
    344         elif agent_data.scene_path is not None:
    345             scene_dir = agent_data.scene_path
    346         if scene_dir is not None:
    347             try:
    348                 PlaybackScene(scene_dir=scene_dir, parent=self.agent_portrait_intro_3d_container, hide=True, sound_start_time=self.sound_start_time if not restart_audio else 0)
    349                 self._is_portrait_loaded = True
    350             except FailedToPlaybackScene as exc:
    351                 logger.exception(exc)
    352 
    353     def _load_agent_portrait_image(self, agent_data, restart_audio = False):
    354         if self._is_portrait_loaded:
    355             return
    356         agent_portrait_width, agent_portrait_height = self._get_agent_portrait_size()
    357         self.agent_portrait_image_sprite = Sprite(name='agent_portrait', parent=self.portrait_wrapper_container, align=uiconst.CENTER, width=agent_portrait_width, height=agent_portrait_height, texturePath=agent_data.image_path)
    358         self._is_portrait_loaded = True
    359         if restart_audio:
    360             self._start_playing_audio()
    361 
    362     def _load_agent_portrait_videos(self, agent_data):
    363         if self._is_portrait_loaded or not agent_data.use_video:
    364             return
    365         agent_portrait_size = self._get_agent_portrait_size()[0]
    366         has_intro_video = agent_data.use_intro_video
    367         if has_intro_video:
    368             self.agent_portrait_intro_video_container = Container(parent=self.portrait_wrapper_container, name='agent_portrait_intro_video_container', align=uiconst.TOTOP_NOPUSH, width=self.portrait_container.width, height=self.portrait_container.height, display=False)
    369             self.agent_portrait_intro_video = StreamingVideoSprite(parent=self.agent_portrait_intro_video_container, name='agent_portrait_intro_video', videoPath=agent_data.intro_video_path, videoLoop=False, align=uiconst.CENTER, width=agent_portrait_size, height=agent_portrait_size, state=uiconst.UI_DISABLED, blendMode=TR2_SBM_NONE, disableAudio=True)
    370             self.agent_portrait_intro_video.Pause()
    371             self.agent_portrait_intro_video_container.display = False
    372         self.agent_portrait_loop_video_container = Container(parent=self.portrait_wrapper_container, name='agent_portrait_loop_video_container', align=uiconst.TOTOP_NOPUSH, width=self.portrait_container.width, height=self.portrait_container.height, display=False)
    373         self.agent_portrait_loop_video = StreamingVideoSprite(parent=self.agent_portrait_loop_video_container, name='agent_portrait_loop_video', videoPath=agent_data.loop_video_path, videoLoop=True, align=uiconst.CENTER, width=agent_portrait_size, height=agent_portrait_size, state=uiconst.UI_DISABLED, blendMode=TR2_SBM_NONE, disableAudio=True)
    374         self.agent_portrait_loop_video_container.display = False
    375         if has_intro_video:
    376             self.agent_portrait_intro_video.OnVideoFinished = self._play_agent_portrait_loop_video
    377             self._play_agent_portrait_intro_video()
    378         else:
    379             self.agent_portrait_intro_video = None
    380             self.agent_portrait_intro_video_container = None
    381             self._play_agent_portrait_loop_video()
    382         self._is_portrait_loaded = True
    383 
    384     def _play_agent_portrait_intro_video(self):
    385         self.agent_portrait_intro_video_container.display = True
    386         self.agent_portrait_intro_video.Play()
    387 
    388     def _play_agent_portrait_loop_video(self):
    389         if self.agent_portrait_intro_video:
    390             self.agent_portrait_intro_video_container.display = False
    391         self.agent_portrait_loop_video_container.display = True
    392         self.agent_portrait_loop_video.Play()
    393 
    394     def _get_sprite_class(self):
    395         if self.style.style_color:
    396             return Sprite
    397         return SpriteThemeColored
    398 
    399     def _set_sprite_color(self, sprite, glow = True):
    400         if not sprite:
    401             return
    402         if self.style.style_color:
    403             sprite.color = self.style.style_color
    404             return
    405         if glow:
    406             sprite.colorType = uiconst.COLORTYPE_UIHILIGHTGLOW
    407         else:
    408             sprite.colorType = uiconst.COLORTYPE_UIHILIGHT
    409 
    410     def _load_portrait_side_lines(self):
    411         position_top = (self.portrait_container.height - AGENT_CONTAINER_SIDE_TEXTURE_HEIGHT) / 2
    412         sprite_class = self._get_sprite_class()
    413         self._clear_container(self.portrait_side_line_left_container)
    414         self._clear_container(self.portrait_side_line_right_container)
    415         self.portrait_side_line_left_container = Container(name='portrait_side_line_left_container', parent=self.portrait_container, align=uiconst.ANCH_TOPLEFT, width=self.portrait_container.width, height=self.portrait_container.height)
    416         portrait_side_line_left = sprite_class(name='portrait_side_line_left', parent=self.portrait_side_line_left_container, texturePath=AGENT_CONTAINER_SIDE_TEXTURE, align=uiconst.ANCH_TOPLEFT, useSizeFromTexture=True, top=position_top, rotation=pi, opacity=AGENT_CONTAINER_SIDE_OPACITY, blendMode=TR2_SBM_ADD)
    417         self._set_sprite_color(portrait_side_line_left, glow=True)
    418         self.portrait_side_line_right_container = Container(name='portrait_side_line_right_container', parent=self.portrait_container, align=uiconst.ANCH_TOPRIGHT, width=self.portrait_container.width, height=self.portrait_container.height)
    419         portrait_side_line_right = sprite_class(name='portrait_side_line_right', parent=self.portrait_side_line_right_container, texturePath=AGENT_CONTAINER_SIDE_TEXTURE, align=uiconst.ANCH_TOPRIGHT, useSizeFromTexture=True, top=position_top + AGENT_CONTAINER_SIDE_TEXTURE_HEIGHT, left=AGENT_CONTAINER_SIDE_TEXTURE_WIDTH, opacity=AGENT_CONTAINER_SIDE_OPACITY, blendMode=TR2_SBM_ADD)
    420         self._set_sprite_color(portrait_side_line_right, glow=True)
    421 
    422     def _load_portrait_side_lines_glow(self):
    423         position_top_glow = (self.portrait_container.height - AGENT_CONTAINER_SIDE_GLOW_TEXTURE_HEIGHT) / 2
    424         sprite_class = self._get_sprite_class()
    425         self._clear_container(self.portrait_side_line_glow_left_container)
    426         self._clear_container(self.portrait_side_line_glow_right_container)
    427         self.portrait_side_line_glow_left_container = Container(name='portrait_side_line_glow_left_container', parent=self.portrait_container, align=uiconst.ANCH_TOPLEFT, width=self.portrait_container.width, height=self.portrait_container.height)
    428         portrait_side_line_glow_left = sprite_class(name='portrait_side_line_glow_left', parent=self.portrait_side_line_glow_left_container, texturePath=AGENT_CONTAINER_SIDE_GLOW_TEXTURE, align=uiconst.ANCH_TOPLEFT, useSizeFromTexture=True, top=position_top_glow, left=-AGENT_CONTAINER_SIDE_GLOW_TEXTURE_WIDTH / 2, blendMode=TR2_SBM_ADD, rotation=pi)
    429         self._set_sprite_color(portrait_side_line_glow_left, glow=False)
    430         self.portrait_side_line_glow_right_container = Container(name='portrait_side_line_glow_right_container', parent=self.portrait_container, align=uiconst.ANCH_TOPRIGHT, width=self.portrait_container.width, height=self.portrait_container.height)
    431         portrait_side_line_glow_right = sprite_class(name='portrait_side_line_glow_right', parent=self.portrait_side_line_glow_right_container, texturePath=AGENT_CONTAINER_SIDE_GLOW_TEXTURE, align=uiconst.ANCH_TOPRIGHT, useSizeFromTexture=True, top=position_top_glow + AGENT_CONTAINER_SIDE_GLOW_TEXTURE_HEIGHT, left=AGENT_CONTAINER_SIDE_GLOW_TEXTURE_WIDTH / 2, blendMode=TR2_SBM_ADD)
    432         self._set_sprite_color(portrait_side_line_glow_right, glow=False)
    433 
    434     def _add_title(self):
    435         self.title_container = Container(name='title_container', parent=self.content_container, align=uiconst.TOTOP, width=self.content_container.width, height=0, padBottom=TITLE_CONTAINER_BOT_PADDING)
    436         title_text_width = self.content_container.width
    437         if self.is_content_expandable:
    438             self.expand_option_container = Container(name='expand_option_container', parent=self.title_container, align=uiconst.TORIGHT, width=EXPAND_OPTION_ICON_SIZE, height=EXPAND_OPTION_ICON_SIZE)
    439             title_text_width = title_text_width - EXPAND_OPTION_ICON_SIZE - TITLE_TO_EXPAND_OPTION_PADDING
    440         self.title_text_container = Container(name='title_text_container', parent=self.title_container, align=uiconst.TOLEFT, width=title_text_width, height=0)
    441         self.title_label = EveLabelLarge(name='title_label', parent=self.title_text_container, text='', width=title_text_width, align=uiconst.TOTOP, opacity=TITLE_OPACITY, blendMode=TR2_SBM_NONE)
    442 
    443     def _load_expand_option_sprite(self, expand):
    444         if not self.is_content_expandable or not self.is_audio_activated:
    445             return
    446         expand_sprite_texture = ICON_TEXT_COLLAPSE if expand else ICON_TEXT_EXPAND
    447         if self._is_ui_element_available(self.expand_option_sprite):
    448             self.expand_option_sprite.Close()
    449         self.expand_option_sprite = Sprite(name='expand_option', parent=self.expand_option_container, align=uiconst.CENTER, width=EXPAND_OPTION_ICON_SIZE, height=EXPAND_OPTION_ICON_SIZE, texturePath=expand_sprite_texture)
    450         self.expand_option_sprite.OnClick = self._expand_content
    451 
    452     def _load_title(self, title):
    453         self.title_label.text = title
    454 
    455     def _load_title_line(self, expand = True):
    456         self._clear_container(self.title_line_container)
    457         if expand or not self.is_content_expandable:
    458             self.title_line_container = Container(name='title_line_container', parent=self.content_container, align=uiconst.TOTOP, width=self.content_container.width, height=LINE_HEIGHT)
    459             self.title_line = LineThemeColored(name='title_line', parent=self.title_line_container, align=uiconst.TOTOP, weight=LINE_HEIGHT)
    460             if self.style:
    461                 self.style_line(self.title_line)
    462 
    463     def _should_collapse_content(self, expand):
    464         return self.is_content_expandable and not expand
    465 
    466     def _should_show_text(self):
    467         return self.text
    468 
    469     def _should_show_hints(self):
    470         return self.are_hints_visible and self.hints
    471 
    472     def _should_show_hints_line(self):
    473         return self._should_show_text() and self._should_show_hints()
    474 
    475     def _get_scroll_bar_color(self):
    476         if self.style is not None:
    477             return self.style.scroll_bar_color
    478         else:
    479             return
    480 
    481     def _load_text(self, expand = True):
    482         self._clear_container(self.text_container)
    483         if not self._should_show_text() or self._should_collapse_content(expand):
    484             return
    485         self.text_container = Container(name='text_container', parent=self.content_container, align=uiconst.TOTOP, width=self.content_container.width, height=0, padTop=TEXT_TOP_PADDING, padBottom=TEXT_BOTTOM_PADDING)
    486         scroll = ScrollContainer(align=uiconst.TOTOP, parent=self.text_container, height=self._get_text_scroll_height(), scrollBarColor=self._get_scroll_bar_color())
    487         self.text_paragraphs_container = ContainerAutoSize(parent=scroll, align=uiconst.TOTOP)
    488         self.paragraph_labels = []
    489         paragraphList = self.GetTextByParagraphs(self.text)
    490         for text in paragraphList:
    491             label = EveLabelLarge(name='text_label', parent=self.text_paragraphs_container, text=text, align=uiconst.TOTOP, opacity=0.0, color=COLOR_LABEL, padBottom=12)
    492             self.paragraph_labels.append(label)
    493 
    494         self.text_paragraphs_container.SetSizeAutomatically()
    495         scroll.height = self._get_text_scroll_height()
    496 
    497     def GetTextByParagraphs(self, text):
    498         paragraphList = text.split('\r\n\r\n')
    499         return paragraphList
    500 
    501     def _load_hints(self, expand = True):
    502         self._clear_container(self.hints_container)
    503         if not self._should_show_hints() or self._should_collapse_content(expand):
    504             return
    505         self.hints_container = ContainerAutoSize(name='hints_container', parent=self.content_container, align=uiconst.TOTOP, alignMode=uiconst.TOTOP, opacity=0.0)
    506         if self._should_show_hints_line():
    507             self._load_hints_line()
    508         self.hint_labels = []
    509         for hint in self.hints:
    510             paragraph_list = self.GetTextByParagraphs(GetByMessageID(hint.text))
    511             hint_container = ContainerAutoSize(name='hint_container', parent=self.hints_container, align=uiconst.TOTOP, alignMode=uiconst.TOTOP, padBottom=12 if paragraph_list else 0)
    512             hint_sprite = self._check_create_hint_sprite(hint, hint_container)
    513             hint_labels_container = ContainerAutoSize(name='hint_labels_container', parent=hint_container, align=uiconst.TOTOP, alignMode=uiconst.TOTOP, minHeight=hint_sprite.height if hint_sprite else 0)
    514             text_height = 0
    515             for text in paragraph_list:
    516                 pad_top = 12 if self.hint_labels else 0
    517                 label = EveLabelLarge(name='hint_text_label', parent=hint_labels_container, text=text, align=uiconst.TOTOP, color=(1, 1, 1, 1), opacity=0.0, padTop=pad_top)
    518                 text_height += label.height + pad_top
    519                 self.hint_labels.append(label)
    520 
    521             hint_labels_container.SetSizeAutomatically()
    522             if hint_sprite and text_height < hint_sprite.height:
    523                 hint_labels_container.padTop += max(0, (hint_sprite.height - text_height) / 2)
    524 
    525         self.hints_container.SetSizeAutomatically()
    526         self.hints_container.UpdateAlignment()
    527 
    528     def _load_hints_line(self):
    529         self.hints_line = LineThemeColored(name='hints_line', parent=self.hints_container, align=uiconst.TOTOP, weight=LINE_HEIGHT, padBottom=12)
    530         if self.style:
    531             self.style_line(self.hints_line)
    532 
    533     def _check_create_hint_sprite(self, hint, parent):
    534         if not hint.iconID:
    535             return
    536         hint_image = self._conversation_svc.get_line_hint_image(hint.iconID)
    537         hint_image_path = hint_image.image_path
    538         hint_image_size = hint.image_size or DEFAULT_HINT_ICON_SIZE
    539         pad_right = 6
    540         hint_sprite_container = Container(name='hint_sprite_container', parent=parent, width=hint_image_size + pad_right, height=hint_image_size, padLeft=-HINT_CELL_PADDING, align=uiconst.TOLEFT)
    541         hint_sprite = Sprite(name='hint_image_icon', parent=hint_sprite_container, width=hint_image_size, height=hint_image_size, texturePath=hint_image_path, state=uiconst.UI_DISABLED, align=uiconst.CENTERLEFT)
    542         if hint_sprite and hint.image_color:
    543             hint_sprite.SetRGBA(*hint.image_color)
    544         return hint_sprite
    545 
    546     def _should_expand_content(self):
    547         language = getattr(session, 'languageID', None)
    548         is_client_localized = not IsPrimaryLanguage(language) if language else True
    549         is_audio_enabled = self.is_audio_activated
    550         return is_client_localized or not is_audio_enabled
    551 
    552     def _is_content_expanded(self):
    553         return self.text_container is not None and not self.text_container.destroyed
    554 
    555     def _expand_content(self):
    556         expand = not self._is_content_expanded()
    557         settings.char.ui.Set(SETTING_CONVERSATION_WINDOW_TEXT_EXPAND, expand)
    558         self.manual_expanded_setting = expand
    559         self._clear_container(self.navigation_button_container)
    560         self._show_expanded_content(expand)
    561         self._load_navigation_buttons()
    562         self._correct_height(style_changed=False)
    563 
    564     def _should_show_navigation_buttons(self):
    565         return self._should_show_next_button() or self._should_show_previous_button() or self._should_show_close_button()
    566 
    567     def _should_show_next_button(self):
    568         return self.are_navigation_buttons_enabled and self._conversation_svc.are_any_conversation_lines_left()
    569 
    570     def _should_show_previous_button(self):
    571         return self.are_navigation_buttons_enabled and self._conversation_svc.are_any_previous_conversation_lines_available()
    572 
    573     def _should_show_close_button(self):
    574         return self.is_close_button_visible and not self._conversation_svc.are_any_conversation_lines_left()
    575 
    576     def _load_navigation_buttons(self):
    577         if not self._should_show_navigation_buttons():
    578             return
    579         self._clear_container(self.navigation_button_container)
    580         self.navigation_button_container = Container(name='navigation_button_container', align=uiconst.TOBOTTOM, parent=self.content_container, height=NAVIGATION_BUTTON_HEIGHT, padTop=NAVIGATION_BUTTON_PADDING_TOP, opacity=0.0)
    581         if self._should_show_hints_line():
    582             self.navigation_button_container.padTop += HINTS_TO_BUTTONS_PADDING
    583         if self._should_show_next_button():
    584             label_text = GetByLabel(NEXT_CONVERSATION_LINE_LABEL)
    585             label_text_width = GetTextWidth(strng=label_text, fontsize=DEFAULT_FONTSIZE)
    586             button_width = label_text_width + 2 * NAVIGATION_BUTTON_LABEL_PADDING
    587             next_button = Button(name='next_button', parent=self.navigation_button_container, align=uiconst.TORIGHT, fixedwidth=button_width, label=label_text, func=self.show_next_conversation, args=())
    588             self.style_button(next_button)
    589         if self._should_show_previous_button():
    590             label_text = GetByLabel(PREVIOUS_CONVERSATION_LINE_LABEL)
    591             label_text_width = GetTextWidth(strng=label_text, fontsize=DEFAULT_FONTSIZE)
    592             button_width = label_text_width + 2 * NAVIGATION_BUTTON_LABEL_PADDING
    593             previous_button = Button(name='previous_button', parent=self.navigation_button_container, align=uiconst.TOLEFT, fixedwidth=button_width, label=label_text, func=self.show_previous_conversation, args=())
    594             self.style_button(previous_button)
    595         if self._should_show_close_button():
    596             label_text = GetByLabel(CONTINUE_BUTTON_LABEL)
    597             label_text_width = GetTextWidth(strng=label_text, fontsize=DEFAULT_FONTSIZE)
    598             button_width = label_text_width + 2 * NAVIGATION_BUTTON_LABEL_PADDING
    599             close_button = Button(name='close_button', parent=self.navigation_button_container, align=uiconst.TORIGHT, fixedwidth=button_width, label=label_text, func=self._on_continue_button, args=())
    600             self.style_button(close_button)
    601 
    602     def _on_continue_button(self, *args):
    603         sm.ScatterEvent('OnClientEvent_ConversationContinueButtonClicked', self.conversation_id)
    604         if self.should_close_on_continue:
    605             self._close_if_open()
    606 
    607     def _clear_navigation_buttons(self):
    608         self._clear_container(self.navigation_button_container)
    609         self.navigation_button_container = None
    610 
    611     def show_completed_ui(self):
    612         self.is_completed = True
    613         if self._is_ui_element_available(self.completed_ui_container):
    614             return
    615         self._load_completed_ui()
    616         self._correct_height()
    617 
    618     def _load_completed_ui(self):
    619         self._clear_container(self.completed_ui_container)
    620         if not self.is_completed:
    621             return
    622         self.completed_ui_container = CompletedConversationUi(name='completed_ui_container', align=uiconst.TOBOTTOM, parent=self.content_container, width=self.content_container.width, height=COMPLETED_UI_HEIGHT, padTop=COMPLETED_UI_PADDING)
    623 
    624     def _clear_completed_ui(self):
    625         self._clear_container(self.completed_ui_container)
    626         self.completed_ui_container = None
    627 
    628     def style_button(self, button):
    629         if self.style.style_color:
    630             button.SetColor(self.style.style_color)
    631         if self.style.label_color:
    632             button.SetLabelColor(self.style.label_color)
    633 
    634     def _correct_height(self, style_changed = False):
    635         title_height = self.title_label.height
    636         self.title_container.height = title_height
    637         self.title_text_container = title_height
    638         title_height = get_total_container_height(self.title_container)
    639         title_line_height = 0
    640         text_height = 0
    641         hints_height = 0
    642         if self._is_ui_element_available(self.title_line_container):
    643             title_line_height = get_total_container_height(self.title_line_container)
    644         if self._is_ui_element_available(self.text_container):
    645             self.text_container.height = self._get_text_scroll_height()
    646             text_height = get_total_container_height(self.text_container)
    647         if self._is_ui_element_available(self.hints_container):
    648             hints_height = get_total_container_height(self.hints_container)
    649         self.content_container.height = title_height + title_line_height + text_height + hints_height
    650         if self._should_show_navigation_buttons() and self._is_ui_element_available(self.navigation_button_container):
    651             navigation_button_height = get_total_container_height(self.navigation_button_container)
    652             self.content_container.height = self.content_container.height + navigation_button_height
    653         if self.is_completed and self._is_ui_element_available(self.completed_ui_container):
    654             completed_ui_height = get_total_container_height(self.completed_ui_container)
    655             self.content_container.height = self.content_container.height + completed_ui_height
    656         self.main_container.height = self._get_main_container_height()
    657         self._apply_fixed_size()
    658         if style_changed:
    659             self._apply_style()
    660         self.style_backgrounds()
    661 
    662     def _apply_fixed_size(self):
    663         height = self.main_container.height + 2 * MAIN_CONTAINER_PADDING_WINDOW + MAIN_CONTAINER_PADDING_TOP
    664         width = self._get_main_container_width()
    665         width, height = self.GetWindowSizeForContentSize(width=width, height=height)
    666         self.SetFixedWidth(width)
    667         self.SetFixedHeight(height)
    668 
    669     def _on_sound_event_completed(self, event_str):
    670         if self.agent_portrait_intro_3d_container is not None:
    671             avatar_context = self.agent_portrait_intro_3d_container.avatarscenecontainer.context
    672             avatar_scene_play_event = avatar_context.GetSceneAudioPlayingEvent() if avatar_context else None
    673         else:
    674             avatar_scene_play_event = None
    675         if not self.sound_playing and not avatar_scene_play_event:
    676             return
    677         if event_str is None or not self.is_audio_activated:
    678             return
    679         current_play_event = self._conversation_svc.get_play_event(self.current_sound_id)
    680         if event_str == current_play_event or event_str == avatar_scene_play_event:
    681             self.sound_playing = False
    682             if self.should_autoprogress:
    683                 self.show_next_conversation(delay=True, play_incoming_transmission_sound=False)
    684 
    685     def _stop_playing_audio(self):
    686         self.sound_playing = False
    687         if self.current_sound_id is None:
    688             return
    689         stop_event = self._conversation_svc.get_stop_event(self.current_sound_id)
    690         if stop_event:
    691             self._audio_svc.SendUIEvent(stop_event)
    692 
    693     def _start_playing_audio(self):
    694         if self.destroyed or session.languageID.lower() not in ('en', 'ko', 'es'):
    695             return
    696         if uicore.layer.systemmenu.isopen:
    697             return
    698         if self.sound_playing:
    699             return
    700         play_event = self._conversation_svc.get_play_event(self.current_sound_id)
    701         if play_event and self.is_audio_activated:
    702             self.sound_playing = True
    703             self.sound_start_time = time.time()
    704             self._audio_svc.SendUIEventWithCallback(play_event)
    705 
    706     def OnAudioActivated(self):
    707         self.is_audio_activated = True
    708         call_after_wallclocktime_delay(self._activate_audio, AUDIO_ACTIVATION_DELAY_SECONDS)
    709 
    710     def _activate_audio(self):
    711         if self.destroyed:
    712             return
    713         if not uicore.layer.systemmenu.isopen:
    714             self._start_playing_audio()
    715         if not self._should_show_navigation_buttons() and self.navigation_button_container:
    716             self._clear_navigation_buttons()
    717             self._correct_height()
    718         expand = self._get_expand_setting()
    719         self._load_expand_option_sprite(expand)
    720 
    721     def OnAudioDeactivated(self):
    722         self.is_audio_activated = False
    723         self._stop_playing_audio()
    724         if self.agent_portrait_intro_3d_container is not None:
    725             self.agent_portrait_intro_3d_container.avatarscenecontainer.context.StopAudio()
    726         did_content_change = False
    727         if self._should_expand_content() and not self._is_content_expanded():
    728             self._show_expanded_content(expand=True)
    729             did_content_change = True
    730         if self._should_show_navigation_buttons() and not self.navigation_button_container:
    731             self._load_navigation_buttons()
    732             did_content_change = True
    733         if did_content_change:
    734             self._correct_height()
    735         if self._is_ui_element_available(self.expand_option_sprite):
    736             self.expand_option_sprite.Close()
    737 
    738     def OnHideUI(self):
    739         self._stop_playing_audio()
    740         if self.agent_portrait_intro_3d_container is not None and self.agent_portrait_intro_3d_container.avatarscenecontainer.context is not None:
    741             self.agent_portrait_intro_3d_container.avatarscenecontainer.context.StopAudio()
    742 
    743     def OnShowUI(self):
    744         if self.agent_portrait_intro_3d_container is not None:
    745             if self._is_using_low_quality_characters():
    746                 if self.current_agent_id is not None and self.current_line is not None:
    747                     logger.info('avatardisplay: Replacing 3D agent with 2D agent')
    748                     self.agent_portrait_intro_3d_container.display = False
    749                     self.agent_portrait_intro_3d_container.Close()
    750                     self.agent_portrait_intro_3d_container = None
    751                     self._is_portrait_loaded = False
    752                     self._load_agent_portrait(self._conversation_svc.get_agent(self.current_agent_id), self.current_line, restart_audio=True)
    753             else:
    754                 logger.info('avatardisplay: Reloading in OnShowUI')
    755                 self.agent_portrait_intro_3d_container.avatarscenecontainer.Reload()
    756         elif self._is_portrait_loaded:
    757             if not self._is_using_low_quality_characters():
    758                 logger.info('avatardisplay: Replacing 2D agent with 3D agent')
    759                 self._is_portrait_loaded = False
    760                 if self.agent_portrait_intro_video_container:
    761                     self.agent_portrait_intro_video_container.Close()
    762                     logger.info('avatardisplay: closing video container')
    763                 if self.agent_portrait_image_sprite:
    764                     self.agent_portrait_image_sprite.Close()
    765                     logger.info('avatardisplay: closing sprite')
    766                 self._load_agent_portrait(self._conversation_svc.get_agent(self.current_agent_id), self.current_line, restart_audio=True)
    767                 self.agent_portrait_intro_3d_container.display = False
    768                 blue.synchro.Sleep(100)
    769                 self.agent_portrait_intro_3d_container.display = True
    770 
    771     def OnSetDevice(self):
    772         self._stop_playing_audio()
    773 
    774     def show_next_conversation(self, delay = False, play_incoming_transmission_sound = True):
    775         if self.showing_next:
    776             return
    777         self.showing_next = True
    778         self.kill_next_conversation_tasklet()
    779         next_line = None
    780         if self._conversation_svc.are_any_conversation_lines_left():
    781             next_line = self._conversation_svc.get_next_conversation_line()
    782         delay_seconds = self.delay_seconds_between_audio_ended_and_show_next
    783         apply_delay = delay and self.is_audio_activated and delay_seconds > 0
    784         self._stop_playing_audio()
    785         if next_line:
    786             if apply_delay:
    787                 self.next_conversation_tasklet = call_after_wallclocktime_delay(self._show_content, delay_seconds, next_line, play_incoming_transmission_sound)
    788             else:
    789                 self._show_content(next_line, play_incoming_transmission_sound)
    790             return
    791         self.close_on_conversation_end(apply_delay, delay_seconds)
    792         self.showing_next = False
    793 
    794     def show_previous_conversation(self):
    795         if self.showing_next:
    796             return
    797         self.showing_next = True
    798         self.kill_next_conversation_tasklet()
    799         if not self._conversation_svc.are_any_previous_conversation_lines_available():
    800             return
    801         self._stop_playing_audio()
    802         previous_line = self._conversation_svc.get_previous_conversation_line()
    803         self._show_content(previous_line)
    804 
    805     def kill_next_conversation_tasklet(self):
    806         if self.next_conversation_tasklet:
    807             self.next_conversation_tasklet.Kill()
    808             self.next_conversation_tasklet = None
    809             if not self._conversation_svc.are_any_previous_conversation_lines_available():
    810                 return
    811             self._conversation_svc.get_previous_conversation_line()
    812 
    813     def close_on_conversation_end(self, apply_delay, delay_seconds):
    814         is_audio_active = self.is_audio_activated
    815         should_close_on_audio_end_with_delay = is_audio_active and self.close_on_audio_end and apply_delay
    816         should_close_on_audio_end_with_no_delay = is_audio_active and self.close_on_audio_end and not apply_delay
    817         if should_close_on_audio_end_with_delay:
    818             call_after_wallclocktime_delay(self._close_if_open, delay_seconds)
    819         elif should_close_on_audio_end_with_no_delay or not is_audio_active:
    820             self._close_if_open()
    821 
    822     def _close_if_open(self):
    823         self.CloseIfOpen()
    824 
    825     def _show_content(self, next_line, play_incoming_transmission_sound = False, play_audio = True):
    826         if self.destroyed:
    827             return
    828         self.next_conversation_tasklet = None
    829         self._is_portrait_loaded = False
    830         self.text = next_line.get_text()
    831         self.hints = next_line.hints
    832         sound_id = next_line.sound_id
    833         agent_id = next_line.agent_id
    834         self.current_agent_id = agent_id
    835         self.current_line = next_line
    836         agent_data = self._conversation_svc.get_agent(agent_id)
    837         agent_name = GetByMessageID(agent_data.name)
    838         previous_style = self.style
    839         new_style = self._get_style_by_agent_id(agent_id)
    840         style_changed = previous_style != new_style
    841         if style_changed:
    842             self.style = new_style
    843         self.current_sound_id = sound_id
    844         if play_audio:
    845             self._start_playing_audio()
    846         self._load_portrait(agent_data, next_line)
    847         self._load_title(agent_name)
    848         self._clear_navigation_buttons()
    849         self._clear_completed_ui()
    850         expand = self._get_expand_setting()
    851         self._show_expanded_content(expand)
    852         self._load_navigation_buttons()
    853         self._load_completed_ui()
    854         self._correct_height(style_changed)
    855         uthread.new(self.animate_text_portrait_in)
    856         self.showing_next = False
    857 
    858     def _get_expand_setting(self):
    859         if not self.is_content_expandable or not self.is_audio_activated:
    860             return True
    861         manual_expanded_setting = settings.char.ui.Get(SETTING_CONVERSATION_WINDOW_TEXT_EXPAND, None)
    862         if manual_expanded_setting is not None:
    863             return manual_expanded_setting
    864         return self._should_expand_content()
    865 
    866     def _show_expanded_content(self, expand):
    867         self._load_expand_option_sprite(expand)
    868         self._load_title_line(expand)
    869         self._load_text(expand)
    870         self._load_hints(expand)
    871 
    872     def _get_style_by_agent_id(self, agent_id):
    873         return AGENT_ID_TO_STYLE.get(agent_id, self.default_style)
    874 
    875     def _apply_style(self):
    876         self.style_line(self.title_line)
    877         self.style_line(self.hints_line)
    878         is_transparent = self.style.style_color is not None
    879         self.style_window_transparency(is_transparent)
    880 
    881     def style_line(self, line):
    882         if line:
    883             line.SetFixedColor(self.style.style_color)
    884             line.opacity = TITLE_LINE_OPACITY
    885 
    886     def style_window_transparency(self, is_transparent):
    887         pass
    888 
    889     def Prepare_Background_(self):
    890         self.sr.underlay = ConversationWindowUnderlay(name='styledBackgroundsContainer', parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED)
    891 
    892     def style_backgrounds(self):
    893         if not self.style:
    894             return
    895         self.sr.underlay.ApplyStyle(self.style)
    896 
    897     def close_window(self):
    898         self._stop_playing_audio()
    899         AnimatedTransmissionWindow.close_window(self)
    900 
    901     def Close(self, *args, **kwargs):
    902         if self.agent_portrait_intro_3d_container is not None:
    903             self.agent_portrait_intro_3d_container.avatarscenecontainer.Close()
    904         self._stop_playing_audio()
    905         self._audio_svc.UnsubscribeFromEventCallback(self._on_sound_event_completed)
    906         AnimatedTransmissionWindow.Close(self, *args, **kwargs)
    907 
    908     def CloseByUser(self, *args, **kwargs):
    909         should_close = self.show_warning_on_first_close()
    910         if not should_close:
    911             return
    912         if not self.destroyed:
    913             AnimatedTransmissionWindow.CloseByUser(self, *args)
    914 
    915     def Minimize(self, *args, **kwargs):
    916         if self.is_neocom_hidden():
    917             return
    918         if self.agent_portrait_intro_3d_container is not None and self.agent_portrait_intro_3d_container.avatarscenecontainer.context is not None:
    919             self.agent_portrait_intro_3d_container.avatarscenecontainer.FadeOutOpacity(duration=0.5)
    920             self.agent_portrait_intro_3d_container.avatarscenecontainer.context.StopAudio()
    921             self.agent_portrait_intro_3d_container.avatarscenecontainer.context.SetMinimized()
    922         self._stop_playing_audio()
    923         self._audio_svc.UnsubscribeFromEventCallback(self._on_sound_event_completed)
    924         super(ConversationWindow, self).Minimize(*args, **kwargs)
    925         self.notify_of_minimization()
    926 
    927     def Maximize(self, *args, **kwargs):
    928         if self.agent_portrait_intro_3d_container is not None:
    929             self.agent_portrait_intro_3d_container.avatarscenecontainer.opacity = 0.0
    930         self._audio_svc.SubscribeToEventCallback(self._on_sound_event_completed)
    931         self._start_playing_audio()
    932         super(ConversationWindow, self).Maximize(*args, **kwargs)
    933         self._correct_height(style_changed=False)
    934         self.notify_of_maximization()
    935         if self.agent_portrait_intro_3d_container is not None and self.agent_portrait_intro_3d_container.avatarscenecontainer.context is not None:
    936             self.agent_portrait_intro_3d_container.avatarscenecontainer.context.SetMaximized()
    937             self.agent_portrait_intro_3d_container.avatarscenecontainer.Reload(self.sound_start_time)
    938 
    939     def notify_of_open(self):
    940         if self.conversation_id is not None:
    941             self._conversation_svc.notify_of_conversation_start(self.conversation_id)
    942 
    943     def notify_of_close(self):
    944         if self.conversation_id is not None:
    945             self._conversation_svc.clear_conversation(self.conversation_id)
    946 
    947     def notify_of_minimization(self):
    948         if self.conversation_id is not None:
    949             self._conversation_svc.notify_of_conversation_minimized(self.conversation_id)
    950 
    951     def notify_of_maximization(self):
    952         if self.conversation_id is not None:
    953             self._conversation_svc.notify_of_conversation_maximized(self.conversation_id)
    954 
    955     def show_warning_on_first_close(self):
    956         if self.destroyed:
    957             return False
    958         if not self.show_warning_on_first_close_setting or not self.show_warning_on_first_close_dialog:
    959             return True
    960         have_conversations_ever_been_closed = settings.char.ui.Get(self.show_warning_on_first_close_setting, False)
    961         if have_conversations_ever_been_closed:
    962             return True
    963         prompt_player_fsd_dialog(self.show_warning_on_first_close_dialog)
    964         settings.char.ui.Set(self.show_warning_on_first_close_setting, True)
    965         return True
    966 
    967     def OnUIScalingChange(self, *args):
    968         if self.destroyed:
    969             return
    970         if self.agent_portrait_intro_3d_container is not None:
    971             self.agent_portrait_intro_3d_container.avatarscenecontainer.Reload()