tutorial.py (20571B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\projectdiscovery\client\projects\subcellular\tutorial.py 2 from __future__ import absolute_import 3 import copy 4 import logging 5 import math 6 import random 7 import carbonui.const as uiconst 8 import localization 9 import uthread 10 from carbonui.primitives.container import Container 11 from carbonui.primitives.sprite import Sprite 12 from carbonui.primitives.transform import Transform 13 from carbonui.uianimations import animations 14 from .categoryselector import CategorySelector 15 from eve.client.script.ui.control import eveLabel 16 from carbonui.control.button import Button 17 from eve.client.script.ui.control.eveLabel import EveCaptionLarge 18 from eve.client.script.ui.tooltips.tooltipUtil import SetTooltipHeaderAndDescription 19 from .info import INFO as PROJECT_INFO 20 from .processingview import ProcessingView 21 from projectdiscovery.client import const 22 from projectdiscovery.client.projects.subcellular import nested_categories_from_json 23 from projectdiscovery.client.util.dialogue import Dialogue 24 from projectdiscovery.client.util.eventlistener import eventlistener, on_event 25 from projectdiscovery.common.exceptions import MissingKeyError, NoConnectionToAPIError 26 from .resultwindow import ResultWindow 27 from .rewardsview import RewardsView 28 from .taskimages import TaskImage 29 logger = logging.getLogger(__name__) 30 31 @eventlistener() 32 33 class Tutorial(Container): 34 MAX_CATEGORIES = PROJECT_INFO['resultSchema']['maxItems'] - 1 35 36 def ApplyAttributes(self, attributes): 37 super(Tutorial, self).ApplyAttributes(attributes) 38 self.service = sm.RemoteSvc('ProjectDiscovery') 39 self.selection = [] 40 self.isGreeting = False 41 self.isSubmitting = False 42 self.project_id = self.service.get_project_id() 43 self.playerState = attributes.get('playerState') 44 self.starting_scale = attributes.get('starting_scale') 45 self.bottom_container = self.parent.parent.FindChild('bottom_container') 46 self.loading = False 47 self.task_number = None 48 self.finishedTaskCount = 0 49 self.setup_layout() 50 51 def setup_layout(self): 52 self.dialogue_container = Container(name='dialogue_container', parent=self.parent, idx=0) 53 self.left_main_container = Container(name='left_main_container', parent=self, align=uiconst.TOLEFT_PROP, width=0.5, clipChildren=True, top=20) 54 self.category_container = Container(name='category_container', parent=self, align=uiconst.TOLEFT_PROP, width=0.5, padTop=45, clipChildren=True) 55 self.task_label = EveCaptionLarge(parent=self.left_main_container, align=uiconst.CENTERTOP, text=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/TaskLabel'), color=(0.48, 0.48, 0.48, 1), top=35) 56 self.explanation_icon = Sprite(parent=self.left_main_container, align=uiconst.TOPRIGHT, width=54, height=54, top=35, left=15, texturePath='res:/UI/Texture/WindowIcons/attention.png', state=uiconst.UI_HIDDEN) 57 self.training_task_image = TaskImage(label=const.Texts.TaskImageLabel, parent=self.left_main_container, align=uiconst.TOPLEFT_PROP, pos=(25, 80, 420, 445), starting_scale=self.starting_scale) 58 self.result_window = ResultWindow(name='ResultWindow', parent=self.parent, align=uiconst.TOALL, opacity=0, isTrainingPhase=True, taskImage=self.training_task_image, starting_scale=self.starting_scale, bottom_container=self.bottom_container) 59 self.rewards_view = RewardsView(parent=self.parent, opacity=0, playerState=self.playerState, bottom_container=self.bottom_container, idx=0, state=uiconst.UI_DISABLED) 60 self.rewards_view.isTraining = True 61 self.rewards_view.tutorial_completed = False 62 self.processing_view = ProcessingView(parent=self.parent, opacity=0, state=uiconst.UI_DISABLED) 63 self.training_category_selector = CategorySelector(categories=nested_categories_from_json(PROJECT_INFO['info']['classes']), parent=self.category_container, state=uiconst.UI_HIDDEN, starting_scale=self.starting_scale) 64 self.main_button_container = Container(name='main_button_container', parent=self.bottom_container, align=uiconst.CENTERBOTTOM, width=355, height=53, bgTexturePath='res:/UI/Texture/classes/ProjectDiscovery/footerBG.png') 65 self.submit_button_container = Container(name='submitButtonContainer', parent=self.main_button_container, width=250, align=uiconst.CENTER, height=40, top=5) 66 self.submit_button = Button(name='submitButton', parent=self.submit_button_container, align=uiconst.CENTER, label=localization.GetByLabel('UI/ProjectDiscovery/SubmitButtonLabel'), fontsize=18, fixedwidth=170, fixedheight=30, func=lambda x: self.submit_training_solution()) 67 Sprite(parent=self.submit_button_container, align=uiconst.CENTERLEFT, width=34, height=20, texturePath='res:/UI/Texture/classes/ProjectDiscovery/submitArrow.png', opacity=0.7) 68 Sprite(parent=Transform(parent=self.submit_button_container, align=uiconst.CENTERRIGHT, width=34, height=20, rotation=math.pi), align=uiconst.CENTERRIGHT, width=34, height=20, texturePath='res:/UI/Texture/classes/ProjectDiscovery/submitArrow.png', opacity=0.7) 69 self.task_id = eveLabel.Label(parent=self, align=uiconst.BOTTOMRIGHT, height=20, opacity=0.5, left=10) 70 self.refresh_task_button = Button(name='refreshTaskButton', parent=self.category_container, align=uiconst.CENTER, func=lambda x: self.reset_task(), idx=0, label='Refresh Task', fixedwidth=120, fixedheight=30, state=uiconst.UI_HIDDEN) 71 72 def get_total_task_count(self): 73 count = 0 74 for level in self.levelList: 75 count += level['tasksToFinishLevel'] 76 77 return count 78 79 def get_finished_task_count(self): 80 count = 1 81 for x in range(0, self.level): 82 count += self.levelList[x]['tasksToFinishLevel'] 83 84 return count 85 86 @on_event(const.Events.QuitTutorialTooltips) 87 def enable_everything_after_tutorial(self): 88 self.enable_ui() 89 90 def get_tutorial_tooltip_steps(self): 91 return [{'owner': self.training_task_image.images_container, 92 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/SampleImageHeader'), 93 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/SampleImageText')}, 94 {'owner': self.training_category_selector, 95 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CategorySelectionHeader'), 96 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CategorySelectionText')}, 97 {'owner': self.training_category_selector.super_categories[0], 98 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NucleusHeader'), 99 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NucleusText')}, 100 {'owner': self.training_category_selector.super_categories[1], 101 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CytoplasmHeader'), 102 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/CytoplasmText')}, 103 {'owner': self.training_category_selector.super_categories[2], 104 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/PeripheryHeader'), 105 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/PeripheryText')}, 106 {'owner': self.training_category_selector.super_categories[3], 107 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/MiscellaneousHeader'), 108 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/MiscellaneousText')}, 109 {'owner': self.training_category_selector.super_categories[4], 110 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NotIdentifiableHeader'), 111 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/NotIdentifiableText')}, 112 {'owner': self.training_task_image.colorFilterContainer, 113 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/ChannelFilterHeader'), 114 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/ChannelFilterText')}, 115 {'owner': self.main_button_container, 116 'header': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/SubmitButtonHeader'), 117 'text': localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/SubmitButtonText')}] 118 119 @on_event(const.Events.ProjectDiscoveryStarted) 120 def skip_tutorial(self, show_dialogue): 121 self.service.skip_tutorial() 122 123 def start(self): 124 self.show_greeting_dialogue() 125 if self.playerState.finishedTutorial: 126 self.playerState = self.service.reset_tutorial() 127 self.level = self.playerState.tutorialLevel 128 self.levelList = copy.deepcopy(const.get_training_task_list(self.project_id)) 129 self.levelDict = copy.deepcopy(self.levelList[self.level]) 130 self.new_training_task() 131 self.finishedTaskCount = self.get_finished_task_count() 132 self.set_task_label() 133 134 def show_greeting_dialogue(self): 135 self.disable_ui() 136 Dialogue(name='greetingDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=340, messageText=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/GreetingText'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/GreetingHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/GreetingLabel'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/GreetingButton'), toHide=self, isTutorial=True) 137 138 def close(self): 139 self.dialogue_container.Close() 140 self.main_button_container.Close() 141 self.result_window.Close() 142 self.processing_view.Close() 143 self.training_task_image.Flush() 144 self.training_task_image.Close() 145 self.training_category_selector.Close() 146 self.rewards_view.Close() 147 self.Close() 148 149 def reset_task(self): 150 if not self.loading: 151 self.loading = True 152 self.refresh_task_button.SetState(uiconst.UI_HIDDEN) 153 self.training_task_image.reset_image() 154 self.training_category_selector.cascade_categories_out() 155 self.training_category_selector.reset_categories() 156 self.new_training_task() 157 self.loading = False 158 159 def new_training_task(self): 160 self.training_task_image.reset_image() 161 self.selection = [] 162 self.hide_explanation_icon() 163 if not self.levelDict: 164 logger.error('tutorial.py::new_training_task:: no training tasks found') 165 return 166 self.choose_random_task() 167 self.explanation = self.levelDict[self.task_number] 168 try: 169 self.task = self.service.new_training_task(self.task_number) 170 except (NoConnectionToAPIError, MissingKeyError): 171 self.disable_ui() 172 self.training_category_selector.cascade_categories_out() 173 self.category_container.SetState(uiconst.UI_NORMAL) 174 self.training_category_selector.SetState(uiconst.UI_DISABLED) 175 self.show_error_message_and_button() 176 return 177 178 self.task_id.SetText(self.task['taskId']) 179 self.delete_task_number() 180 self.set_categories_to_solution() 181 self.set_task_label() 182 sm.ScatterEvent(const.Events.NewTask, self.task) 183 self.training_category_selector.cascade_categories_in() 184 self.training_category_selector.SetState(uiconst.UI_NORMAL) 185 self.enable_ui() 186 187 def show_error_message_and_button(self): 188 self.training_task_image.hide_loading_wheel() 189 self.training_task_image.show_error_message() 190 self.refresh_task_button.SetState(uiconst.UI_NORMAL) 191 192 def set_task_label(self): 193 self.task_label.SetText(localization.GetByLabel('UI/ProjectDiscovery/Subcellular/Tutorial/TaskLabel') + ' ' + str(self.finishedTaskCount) + '/' + str(self.get_total_task_count())) 194 195 def set_categories_to_solution(self): 196 relevant_categories = list(self.task['solution']) 197 for super_cat in self.training_category_selector.super_categories: 198 for sub in super_cat.sub_categories: 199 if sub.id in self.task['solution']: 200 relevant_categories.extend(sub.excludes) 201 202 for super_cat in self.training_category_selector.super_categories: 203 for sub in super_cat.sub_categories: 204 if sub.id in self.task['solution']: 205 temp_list = list(super_cat.sub_categories) 206 if sub in temp_list: 207 temp_list.remove(sub) 208 max_cats = 2 209 if len(temp_list) < max_cats: 210 max_cats = len(temp_list) 211 for n in range(0, max_cats): 212 item = random.choice(temp_list) 213 relevant_categories.append(item.id) 214 if item in temp_list: 215 temp_list.remove(item) 216 217 break 218 219 for super_cat in self.training_category_selector.super_categories: 220 for sub in super_cat.sub_categories: 221 if sub.id not in relevant_categories: 222 sub.set_unavailable() 223 sub.set_unclickable() 224 else: 225 sub.set_clickable() 226 sub.set_available() 227 228 def delete_task_number(self): 229 if self.task_number: 230 del self.levelDict[self.task_number] 231 self.task_number = None 232 233 def choose_random_task(self): 234 if not self.task_number: 235 self.task_number = random.choice(self.levelDict.keys()[1:]) 236 237 def hide_explanation_icon(self): 238 self.explanation_icon.SetAlpha(0) 239 self.explanation_icon.SetState(uiconst.UI_HIDDEN) 240 241 def open_error_dialogue(self): 242 self.disable_ui() 243 self.dialogue = Dialogue(name='ErrorDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=215, messageText=localization.GetByLabel('UI/ProjectDiscovery/NoCategorySelectedErrorMessage'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/NoCategorySelectedHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/NotificationHeader'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/ErrorButton'), toHide=self) 244 245 def open_too_many_categories_selected_dialogue(self): 246 self.disable_ui() 247 self.dialogue = Dialogue(name='ErrorDialogue', parent=self.dialogue_container, align=uiconst.CENTER, width=450, height=215, messageText=localization.GetByLabel('UI/ProjectDiscovery/TooManyCategoriesMessage'), messageHeaderText=localization.GetByLabel('UI/ProjectDiscovery/TooManyCategoriesHeader'), label=localization.GetByLabel('UI/ProjectDiscovery/NotificationHeader'), buttonLabel=localization.GetByLabel('UI/ProjectDiscovery/ErrorButton'), toHide=self) 248 249 def disable_ui(self): 250 self.left_main_container.state = uiconst.UI_DISABLED 251 self.category_container.state = uiconst.UI_DISABLED 252 self.main_button_container.state = uiconst.UI_DISABLED 253 self.training_category_selector.state = uiconst.UI_DISABLED 254 255 @on_event(const.Events.EnableUI) 256 def enable_ui(self): 257 self.training_task_image.state = uiconst.UI_NORMAL 258 self.left_main_container.state = uiconst.UI_NORMAL 259 self.category_container.state = uiconst.UI_NORMAL 260 self.main_button_container.state = uiconst.UI_NORMAL 261 self.training_category_selector.state = uiconst.UI_NORMAL 262 263 def submit_training_solution(self): 264 if self.isSubmitting: 265 return 266 if not self.selection: 267 self.open_error_dialogue() 268 return 269 self.isSubmitting = True 270 self.finishedTaskCount += 1 271 sm.GetService('audio').SendUIEvent(const.Sounds.MainImageLoopStop) 272 classification = list(set([ cat['id'] for cat in self.selection if not cat['excluded'] ])) 273 self.result = {'playerSelection': classification, 274 'task': {'solution': self.task['solution']}} 275 SetTooltipHeaderAndDescription(targetObject=self.explanation_icon, headerText='', descriptionText=self.explanation) 276 self.result_window.assign_result(self.result) 277 self.training_category_selector.cascade_categories_out() 278 self.training_category_selector.state = uiconst.UI_DISABLED 279 animations.FadeOut(self.main_button_container) 280 animations.FadeOut(self.training_task_image.colorFilterContainer) 281 animations.FadeOut(self.task_label) 282 animations.FadeOut(self.refresh_task_button) 283 self.training_task_image.start_transmission_animation() 284 self.left_main_container.state = uiconst.UI_DISABLED 285 self.category_container.state = uiconst.UI_DISABLED 286 self.main_button_container.state = uiconst.UI_DISABLED 287 288 @on_event(const.Events.TransmissionFinished) 289 def on_transmission_finished(self): 290 self.left_main_container.state = uiconst.UI_NORMAL 291 self.result_window.open() 292 self.result_window.show_result() 293 animations.FadeIn(self.training_task_image.colorFilterContainer) 294 self.show_and_blink_explanation_icon() 295 296 def show_and_blink_explanation_icon(self): 297 self.explanation_icon.SetAlpha(0) 298 self.explanation_icon.SetState(uiconst.UI_NORMAL) 299 animations.FadeIn(self.explanation_icon, duration=1, loops=5, curveType=uiconst.ANIM_OVERSHOT5) 300 301 def is_tutorial_finished(self): 302 self.playerState = self.service.get_player_state() 303 return self.playerState.tutorialLevel >= self.get_number_of_levels() 304 305 def get_number_of_levels(self): 306 return len(self.levelList) 307 308 @on_event(const.Events.ContinueFromTrainingResult) 309 def on_training_result_closed(self): 310 self.isSubmitting = False 311 self.increment_level_if_needed() 312 uthread.new(self.new_training_task) 313 animations.FadeIn(self, duration=1) 314 animations.FadeIn(self.main_button_container) 315 animations.FadeIn(self.training_task_image.colorFilterContainer) 316 animations.FadeIn(self.task_label, timeOffset=1) 317 animations.FadeIn(self.refresh_task_button) 318 self.training_task_image.state = uiconst.UI_NORMAL 319 self.left_main_container.state = uiconst.UI_NORMAL 320 self.category_container.state = uiconst.UI_NORMAL 321 self.main_button_container.state = uiconst.UI_NORMAL 322 323 def end_tutorial(self): 324 if self.service.give_tutorial_rewards(): 325 result = {'XP_Reward': self.service.get_tutorial_xp_reward(), 326 'LP_Reward': 0, 327 'ISK_Reward': 0, 328 'AK_Reward': 0, 329 'playerState': self.service.get_player_state(), 330 'player': {'score': 0.5}, 331 'isTraining': True} 332 self.rewards_view.tutorial_completed = True 333 sm.ScatterEvent(const.Events.CloseResult, result) 334 self.processing_view.start() 335 settings.char.ui.Set('loadStatisticsAfterSubmission', True) 336 else: 337 sm.ScatterEvent(const.Events.ProjectDiscoveryStarted, True) 338 self.Close() 339 340 def increment_level_if_needed(self): 341 self.levelDict['tasksToFinishLevel'] -= 1 342 if self.levelDict['tasksToFinishLevel'] <= 0: 343 self.playerState = self.service.increase_tutorial_level() 344 if self.is_tutorial_finished(): 345 self.end_tutorial() 346 else: 347 self.level = self.playerState.tutorialLevel 348 self.levelDict = copy.deepcopy(self.levelList[self.level]) 349 350 def _update_excluded(self, excluder): 351 excluded = set() 352 for cat in self.selection: 353 excluded.update(cat.get('excludes', [])) 354 355 sm.ScatterEvent(const.Events.ExcludeCategories, excluder, excluded) 356 357 @on_event(const.Events.CategoryChanged) 358 def on_category_changed(self, hexagon): 359 if hexagon.category['selected']: 360 if len(self.selection) > self.MAX_CATEGORIES: 361 hexagon.set_unselected() 362 self.open_too_many_categories_selected_dialogue() 363 return 364 self.selection.append(hexagon.category) 365 elif hexagon.category in self.selection: 366 self.selection.remove(hexagon.category) 367 self._update_excluded(hexagon.category['id'])