repository.py (24270B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\achievement_system\client\repository\repository.py 2 import blue 3 from datetime import datetime, timedelta 4 from achievement_system.client.api.error import RewardAlreadyClaimedError, ServiceUnavailableError, GenericInternalServerError 5 from achievement_system.client.model.definition.reward.identifier import RewardDefinitionIdentifier 6 from achievement_system.client.repository.activity.repository import AchievementActivityRepository 7 from carbon.common.script.sys.serviceConst import ROLE_QA 8 import logging 9 from achievement_system.client.api.signal import on_achievement_completed, on_achievement_progressed, on_achievement_milestone_reached, on_category_progressed, on_category_milestone_reached, on_definitions_loaded, on_admin_achievement_reset, on_admin_achievement_reset_all, on_achievement_milestone_reward_claimed, on_category_milestone_reward_claimed 10 from achievement_system.client.model.definition.achievement.identifier import AchievementDefinitionIdentifier 11 from achievement_system.client.model.definition.achievement.milestone.identifier import AchievementMilestoneDefinitionIdentifier 12 from achievement_system.client.model.definition.category.identifier import CategoryDefinitionIdentifier 13 from achievement_system.client.model.definition.category.milestone.identifier import CategoryMilestoneDefinitionIdentifier 14 from achievement_system.client.model.state.achievement import CachedAchievementStateAttributes, AchievementStateAttributes 15 from achievement_system.client.model.state.unclaimed_rewards import CachedUnclaimedRewards, UnclaimedAchievementRewards, UnclaimedCategoryRewards 16 from achievement_system.client.model.state.achievement_score import CachedAchievementScore 17 from achievement_system.client.model.state.category import CategoryStateAttributes, CachedCategoryStateAttributes 18 from achievement_system.client.model.state.checklist import Checklist 19 from achievement_system.client.repository.flatbuffer.repository import AchievementDefinitionsRepository 20 from achievement_system.client.repository.message_bus.notice_listener import AchievementNoticeListener 21 from achievement_system.client.repository.message_bus.request_messenger import AchievementRequestMessenger 22 from achievement_system.client.repository.message_bus import notice_listener_signal 23 from achievement_system.common.const import LOGGER_NAME 24 from character.client.identifier import CharacterIdentifier 25 from locks.singleFlight import single_flight 26 from semantic_version import SemanticVersion 27 from featuremanagement.client.flag import flag_bool 28 from signals import Signal 29 logger = logging.getLogger(LOGGER_NAME) 30 if False: 31 from typing import Dict 32 from carbon.common.script.sys.serviceManager import ServiceManager 33 from achievement_system.client.repository.test.setup import MockServiceManager 34 _STATE_CACHE_TTL_IN_SECONDS = 600 35 _DELAY_BEFORE_MILESTONE_REACHED_SIGNAL_IN_SECONDS = 1 36 37 class AchievementRepository(object): 38 __startupdependencies__ = ['publicGatewaySvc'] 39 __notifyevents__ = ['OnSessionChanged'] 40 41 def __init__(self, public_gateway, service_manager, are_unreleased_achievements_visible, on_unreleased_achievements_visible_changed, tracked_achievements_setting, pinned_achievements_setting, recently_progressed_achievements_setting, recently_completed_achievements_setting): 42 logger.info('initializing repository...') 43 self._service_manager = service_manager 44 self._on_unreleased_achievements_visible_changed_signal = on_unreleased_achievements_visible_changed 45 self._are_unreleased_achievements_visible = are_unreleased_achievements_visible 46 self._definitions_repository = AchievementDefinitionsRepository(public_gateway, are_unreleased_achievements_visible) 47 self._activity_repository = AchievementActivityRepository(self._service_manager, tracked_achievements_setting, pinned_achievements_setting, recently_progressed_achievements_setting, recently_completed_achievements_setting) 48 self._character_request_messenger = AchievementRequestMessenger(public_gateway) 49 self._notice_listener = AchievementNoticeListener(public_gateway) 50 notice_listener_signal.on_achievement_completed_notice.connect(self._on_achievement_completed) 51 notice_listener_signal.on_achievement_cumulative_progressed_notice.connect(self._on_achievement_cumulative_progressed) 52 notice_listener_signal.on_achievement_checklist_progressed_notice.connect(self._on_achievement_checklist_progressed) 53 notice_listener_signal.on_check_version_notice.connect(self._on_check_version) 54 notice_listener_signal.on_achievement_milestone_reached_notice.connect(self._on_achievement_milestone_reached) 55 notice_listener_signal.on_category_milestone_reached_notice.connect(self._on_category_milestone_reached) 56 notice_listener_signal.on_category_progressed_notice.connect(self._on_category_progressed) 57 self._on_unreleased_achievements_visible_changed_signal.connect(self._on_unreleased_achievements_visible_changed) 58 self._definitions_repository.on_content_updated.connect(self._on_definitions_updated) 59 self._definitions_repository.on_no_content_updated.connect(self._on_definitions_updated_noop) 60 self._achievement_states = {} 61 self._category_states = {} 62 self._unclaimed_rewards = None 63 self._character_scores = {} 64 self._service_manager.RegisterNotify(self) 65 66 def __del__(self): 67 notice_listener_signal.on_achievement_completed_notice.disconnect(self._on_achievement_completed) 68 notice_listener_signal.on_achievement_cumulative_progressed_notice.disconnect(self._on_achievement_cumulative_progressed) 69 notice_listener_signal.on_achievement_checklist_progressed_notice.disconnect(self._on_achievement_checklist_progressed) 70 notice_listener_signal.on_check_version_notice.disconnect(self._on_check_version) 71 notice_listener_signal.on_achievement_milestone_reached_notice.disconnect(self._on_achievement_milestone_reached) 72 notice_listener_signal.on_category_milestone_reached_notice.disconnect(self._on_category_milestone_reached) 73 notice_listener_signal.on_category_progressed_notice.disconnect(self._on_category_progressed) 74 self._on_unreleased_achievements_visible_changed_signal.disconnect(self._on_unreleased_achievements_visible_changed) 75 self._definitions_repository.on_content_updated.disconnect(self._on_definitions_updated) 76 self._definitions_repository.on_no_content_updated.disconnect(self._on_definitions_updated_noop) 77 self._service_manager.UnregisterNotify(self) 78 79 @property 80 def definitions(self): 81 return self._definitions_repository 82 83 @property 84 def activity(self): 85 return self._activity_repository 86 87 @single_flight 88 def get_achievement_state(self, achievement_identifier): 89 self._cache_achievement_state(achievement_identifier=achievement_identifier) 90 if achievement_identifier in self._achievement_states: 91 return self._achievement_states[achievement_identifier].achievement_state 92 93 @single_flight 94 def get_category_state(self, category_identifier): 95 self._cache_category_state(category_identifier=category_identifier) 96 if category_identifier in self._category_states: 97 return self._category_states[category_identifier].category_state 98 99 @single_flight 100 def get_achievement_score(self, character_id): 101 self._cache_achievement_score(character_id) 102 if character_id in self._character_scores: 103 return self._character_scores[character_id].score 104 return 0 105 106 @single_flight 107 def get_unclaimed_rewards_for_achievement(self, achievement_id): 108 self._cache_unclaimed_rewards() 109 if self._unclaimed_rewards and achievement_id in self._unclaimed_rewards.achievements: 110 return self._unclaimed_rewards.achievements[achievement_id] 111 112 def is_reward_claimed_for_achievement_milestone(self, achievement_id, milestone_id, reward_id): 113 achievement_state = self.get_achievement_state(achievement_id) 114 if not achievement_state: 115 return False 116 if not achievement_state.is_milestone_reached(milestone_id): 117 return False 118 unclaimed_achievement_rewards = self.get_unclaimed_rewards_for_achievement(achievement_id) 119 if not unclaimed_achievement_rewards: 120 return True 121 unclaimed_milestone_rewards = unclaimed_achievement_rewards.unclaimed_rewards_per_milestone.get(milestone_id, []) 122 return reward_id not in unclaimed_milestone_rewards 123 124 @single_flight 125 def get_unclaimed_rewards_for_category(self, category_id): 126 self._cache_unclaimed_rewards() 127 if self._unclaimed_rewards and category_id in self._unclaimed_rewards.categories: 128 return self._unclaimed_rewards.categories[category_id] 129 130 @single_flight 131 def get_all_unclaimed_rewards(self): 132 self._cache_unclaimed_rewards() 133 if self._unclaimed_rewards: 134 return (self._unclaimed_rewards.achievements, self._unclaimed_rewards.categories) 135 136 @single_flight 137 def get_unclaimed_rewards_count(self): 138 reward_count = 0 139 achievement_rewards, category_rewards = self.get_all_unclaimed_rewards() 140 for x in achievement_rewards.values(): 141 reward_count += x.get_unclaimed_rewards_count() 142 143 for x in category_rewards.values(): 144 reward_count += x.get_unclaimed_rewards_count() 145 146 return reward_count 147 148 @single_flight 149 def get_unclaimed_rewards_row_count(self): 150 try: 151 reward_count = 0 152 achievement_rewards, category_rewards = self.get_all_unclaimed_rewards() 153 for x in achievement_rewards.values(): 154 reward_count += len(x.unclaimed_rewards_per_milestone) 155 156 for x in category_rewards.values(): 157 reward_count += len(x.unclaimed_rewards_per_milestone) 158 159 return reward_count 160 except (ServiceUnavailableError, GenericInternalServerError): 161 return 0 162 163 @single_flight 164 def claim_rewards_for_achievement(self, achievement_id, milestone_id): 165 try: 166 self._character_request_messenger.claim_achievement_milestone_rewards_request(achievement_id, milestone_id) 167 self._unclaimed_rewards = None 168 on_achievement_milestone_reward_claimed(achievement_id=achievement_id, milestone_id=milestone_id) 169 except RewardAlreadyClaimedError: 170 logger.info('reward already claimed, ignoring - probably just clicking too fast') 171 except Exception as e: 172 logger.error('unable to claim achievement rewards', exc_info=e) 173 174 @single_flight 175 def claim_rewards_for_category(self, category_id, milestone_id): 176 try: 177 self._character_request_messenger.claim_category_milestone_rewards_request(category_id, milestone_id) 178 on_category_milestone_reward_claimed(category_id=category_id, milestone_id=milestone_id) 179 except RewardAlreadyClaimedError: 180 logger.info('reward already claimed, ignoring - probably just clicking too fast') 181 except Exception as e: 182 logger.error('unable to claim category rewards', exc_info=e) 183 184 def are_definitions_loaded(self): 185 return self.definitions and self.definitions.loaded() 186 187 def are_definitions_loading(self): 188 return self.definitions.request_in_progress 189 190 def reload_definitions(self, force_operation = False): 191 if not self.are_definitions_loaded() or force_operation: 192 logger.info('Reloading definitions...') 193 self.definitions.admin_clear_memory_cache() 194 self.definitions.check_version_and_prime(None) 195 196 @single_flight 197 def admin_reset_all(self): 198 if not session.role & ROLE_QA: 199 return 200 self._character_request_messenger.admin_reset_all_request() 201 self._clear_character_session_cache() 202 on_admin_achievement_reset_all() 203 204 @single_flight 205 def admin_add_cumulative_progress_to_achievement(self, achievement_identifier, progress_delta): 206 if not session.role & ROLE_QA: 207 return 208 self._character_request_messenger.admin_add_cumulative_progress_request(achievement_identifier, progress_delta) 209 210 @single_flight 211 def admin_add_checklist_progress_to_achievement(self, achievement_identifier, checklist_delta): 212 if not session.role & ROLE_QA: 213 return 214 self._character_request_messenger.admin_add_checklist_progress_request(achievement_identifier, checklist_delta) 215 216 @single_flight 217 def admin_add_set_progress_to_achievement(self, achievement_identifier, set_delta): 218 if not session.role & ROLE_QA: 219 return 220 self._character_request_messenger.admin_add_set_progress_request(achievement_identifier, set_delta) 221 222 @single_flight 223 def admin_complete_achievement(self, achievement_identifier): 224 if not session.role & ROLE_QA: 225 return 226 self._character_request_messenger.admin_complete_achievement_request(achievement_identifier) 227 228 @single_flight 229 def admin_complete_achievements_in_category(self, category_id): 230 if not session.role & ROLE_QA: 231 return 232 for achievement_entry in self.definitions.get_achievement_entries_recursive(category_id): 233 try: 234 self._character_request_messenger.admin_complete_achievement_request(achievement_entry.identifier) 235 except Exception as e: 236 logger.error('unable to complete achievement %s' % achievement_entry.identifier, exc_info=e) 237 238 self._clear_character_session_cache() 239 240 @single_flight 241 def admin_reset_achievement(self, achievement_identifier): 242 if not session.role & ROLE_QA: 243 return 244 self._character_request_messenger.admin_reset_achievement_request(achievement_identifier) 245 self._achievement_states.pop(achievement_identifier, None) 246 on_admin_achievement_reset(achievement_identifier) 247 248 @single_flight 249 def admin_reset_achievements_in_category(self, category_id): 250 if not session.role & ROLE_QA: 251 return 252 for achievement_entry in self.definitions.get_achievement_entries_recursive(category_id): 253 try: 254 self.admin_reset_achievement(achievement_entry.identifier) 255 except Exception as e: 256 logger.error('unable to reset achievement %s' % achievement_entry.identifier, exc_info=e) 257 258 def admin_clear_disk_cache(self): 259 self.definitions.admin_clear_disk_cache() 260 261 def _cache_achievement_state(self, achievement_identifier): 262 if achievement_identifier not in self._achievement_states or self._achievement_states[achievement_identifier].is_expired: 263 self._make_achievement_state_request(achievement_identifier) 264 265 def _refresh_cached_achievement_state(self, achievement_identifier, completed_date, checklist_value, progress_value, reached_milestones): 266 if achievement_identifier not in self._achievement_states: 267 return 268 old_achievement_state = self._achievement_states[achievement_identifier].achievement_state 269 new_completion_date = completed_date if completed_date is not None else old_achievement_state.completed_date 270 new_checklist_value = checklist_value if checklist_value is not None else old_achievement_state.checklist 271 new_progress_value = progress_value if progress_value is not None else old_achievement_state.progress 272 new_reached_milestones = reached_milestones if reached_milestones is not None else old_achievement_state.reached_milestones 273 new_achievement_state = AchievementStateAttributes(new_completion_date, new_checklist_value, new_progress_value, new_reached_milestones) 274 self._achievement_states[achievement_identifier] = CachedAchievementStateAttributes(achievement_state=new_achievement_state, expiration=datetime.now() + timedelta(seconds=_STATE_CACHE_TTL_IN_SECONDS)) 275 return new_achievement_state 276 277 def _cache_category_state(self, category_identifier): 278 if category_identifier not in self._category_states or self._category_states[category_identifier].is_expired: 279 self._make_category_state_request(category_identifier) 280 281 def _cache_achievement_score(self, character_id): 282 if character_id not in self._character_scores or self._character_scores[character_id].is_expired: 283 self._make_character_score_request(character_id) 284 285 def _cache_unclaimed_rewards(self): 286 if self._unclaimed_rewards is None or self._unclaimed_rewards.is_expired: 287 self._make_unclaimed_rewards_request() 288 289 def _make_achievement_state_request(self, achievement_identifier): 290 try: 291 achievement_state = self._character_request_messenger.get_achievement_request(achievement_id=achievement_identifier) 292 self._achievement_states[achievement_identifier] = CachedAchievementStateAttributes(achievement_state=achievement_state, expiration=datetime.now() + timedelta(seconds=_STATE_CACHE_TTL_IN_SECONDS)) 293 except Exception as e: 294 logger.error('unable to cache achievement state', exc_info=e) 295 296 def _make_category_state_request(self, category_identifier): 297 try: 298 category_state = self._character_request_messenger.get_category_state_request(category_id=category_identifier) 299 self._category_states[category_identifier] = CachedCategoryStateAttributes(category_state=category_state, expiration=datetime.now() + timedelta(seconds=_STATE_CACHE_TTL_IN_SECONDS)) 300 except Exception as e: 301 logger.error('unable to cache category state', exc_info=e) 302 303 def _make_character_score_request(self, character_id): 304 try: 305 character_score = self._character_request_messenger.get_achievement_score_request(character_id=character_id) 306 self._character_scores[character_id] = CachedAchievementScore(score=character_score, expiration=datetime.now() + timedelta(seconds=_STATE_CACHE_TTL_IN_SECONDS)) 307 except Exception as e: 308 logger.error('unable to cache achievement character score', exc_info=e) 309 310 def _make_unclaimed_rewards_request(self): 311 try: 312 page = '' 313 unclaimed_achievement_rewards = {} 314 unclaimed_category_rewards = {} 315 while page is not None: 316 unclaimed_achievement_rewards_page, unclaimed_category_rewards_page, page = self._character_request_messenger.get_unclaimed_rewards_request(page) 317 for entry in unclaimed_achievement_rewards_page: 318 if entry.achievement_id not in unclaimed_achievement_rewards: 319 unclaimed_achievement_rewards[entry.achievement_id] = UnclaimedAchievementRewards() 320 unclaimed_achievement_rewards[entry.achievement_id].add_unclaimed_rewards_for_milestone(milestone_id=entry.milestone_id, reward_ids=entry.unclaimed_reward_ids) 321 322 for entry in unclaimed_category_rewards_page: 323 if entry.category_id not in unclaimed_category_rewards: 324 unclaimed_category_rewards[entry.category_id] = UnclaimedCategoryRewards() 325 unclaimed_category_rewards[entry.category_id].add_unclaimed_rewards_for_milestone(milestone_id=entry.milestone_id, reward_ids=entry.unclaimed_reward_ids) 326 327 self._unclaimed_rewards = CachedUnclaimedRewards(unclaimed_achievement_rewards=unclaimed_achievement_rewards, unclaimed_category_rewards=unclaimed_category_rewards, expiration=datetime.now() + timedelta(seconds=_STATE_CACHE_TTL_IN_SECONDS)) 328 except ServiceUnavailableError as e: 329 logger.info('unable to cache unclaimed rewards - ServiceUnavailableError silenced', exc_info=e) 330 except Exception as e: 331 logger.error('unable to cache unclaimed rewards', exc_info=e) 332 333 def _on_achievement_completed(self, achievement_id): 334 self._achievement_states.pop(achievement_id, None) 335 self.get_achievement_state(achievement_identifier=achievement_id) 336 character_id = CharacterIdentifier(session.charid) 337 self._character_scores.pop(character_id, None) 338 self.get_achievement_score(character_id) 339 on_achievement_completed(achievement_id) 340 341 def _on_achievement_cumulative_progressed(self, achievement_id, value): 342 if achievement_id in self._achievement_states: 343 achievement_state = self._refresh_cached_achievement_state(achievement_id, completed_date=None, checklist_value=None, progress_value=value, reached_milestones=None) 344 else: 345 achievement_state = self.get_achievement_state(achievement_identifier=achievement_id) 346 on_achievement_progressed(achievement_id, achievement_state) 347 348 def _on_achievement_checklist_progressed(self, achievement_id, checklist): 349 if achievement_id in self._achievement_states: 350 achievement_state = self._refresh_cached_achievement_state(achievement_id, completed_date=None, checklist_value=checklist, progress_value=None, reached_milestones=None) 351 else: 352 achievement_state = self.get_achievement_state(achievement_identifier=achievement_id) 353 on_achievement_progressed(achievement_id, achievement_state) 354 355 def _on_category_progressed(self, category_id, _score): 356 self._category_states.pop(category_id, None) 357 self.get_category_state(category_identifier=category_id) 358 on_category_progressed(category_id) 359 360 def _on_achievement_milestone_reached(self, achievement_id, milestone_id): 361 self._achievement_states.pop(achievement_id, None) 362 self._character_scores.pop(CharacterIdentifier(session.charid), None) 363 self.get_achievement_state(achievement_identifier=achievement_id) 364 try: 365 blue.pyos.synchro.SleepWallclock(_DELAY_BEFORE_MILESTONE_REACHED_SIGNAL_IN_SECONDS * 1000.0) 366 except Exception: 367 logger.warning('could not sleep in _on_achievement_milestone_reached') 368 finally: 369 self._unclaimed_rewards = None 370 self.get_unclaimed_rewards_for_achievement(achievement_id) 371 on_achievement_milestone_reached(achievement_id, milestone_id) 372 373 def _on_category_milestone_reached(self, category_id, milestone_id): 374 self._category_states.pop(category_id, None) 375 self.get_category_state(category_identifier=category_id) 376 try: 377 blue.pyos.synchro.SleepWallclock(_DELAY_BEFORE_MILESTONE_REACHED_SIGNAL_IN_SECONDS * 1000.0) 378 except Exception: 379 logger.warning('could not sleep in _on_category_milestone_reached') 380 finally: 381 self._unclaimed_rewards = None 382 self.get_unclaimed_rewards_for_category(category_id) 383 on_category_milestone_reached(category_id, milestone_id) 384 385 def _on_check_version(self, version): 386 logger.info('check version notice received with version %s', version) 387 self.definitions.check_version_and_prime(version) 388 389 def _on_definitions_updated(self, *args): 390 logger.info('definitions loaded') 391 on_definitions_loaded() 392 393 def _on_definitions_updated_noop(self, *args): 394 logger.info('definitions loaded - everything was already up-to-date') 395 396 def _on_unreleased_achievements_visible_changed(self, *args): 397 logger.info('unreleased achievements visibility changed, reloading definitions') 398 self.reload_definitions(True) 399 400 def OnSessionChanged(self, _is_remote, _session, change): 401 if 'charid' in change: 402 self._clear_character_session_cache() 403 if self._definitions_repository.get_current_version_in_memory() is None: 404 logger.info("definitions aren't loaded yet as character enters game, manually fetching") 405 self._definitions_repository.check_version_and_prime(None) 406 407 def _clear_character_session_cache(self): 408 self._achievement_states = {} 409 self._category_states = {} 410 self._unclaimed_rewards = None 411 412 def _clear_caches(self): 413 self._clear_character_session_cache() 414 self._character_scores = {}