goalsController.py (15216B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\dailygoals\client\goalsController.py 2 import uthread2 3 import threadutils 4 import logging 5 from carbon.common.script.sys.serviceConst import ROLE_QA 6 from dailygoals.client.featureFlag import are_daily_goals_enabled 7 from dailygoals.client.goalMessenger import DailyGoalMessenger 8 from dailygoals.client.mockMessenger import MockDailyGoalMessenger 9 import dailygoals.client.goalMessengerSignals as messenger_signals 10 from dailygoals.client.const import DailyGoalCategory, BATCH_SIZE 11 import dailygoals.client.goalSignals as dailyGoalSignals 12 from dailygoals.client.goal import DailyGoal 13 from eve.common.script.util import notificationconst 14 from dailygoals.client.errors import GoalNotRedeemableToCurrentLocation, GoalMessengerError 15 from assetholding.client.assetholdingSignals import on_entitlement_redeemed 16 from assetholding.client.assetholdingController import AssetHoldingController 17 import dailygoals.client.qa_settings as daily_goals_qa_settings 18 from omega.client.clone_grade_svc import get_clone_grade_svc 19 logger = logging.getLogger('dailygoals') 20 21 class DailyGoalsController(object): 22 __notifyevents__ = ['OnCharacterSelected'] 23 _instance = None 24 25 def __init__(self): 26 if daily_goals_qa_settings.daily_goals_use_mock.get(): 27 self._daily_goal_messenger = MockDailyGoalMessenger.get_instance(sm.GetService('publicGatewaySvc')) 28 else: 29 self._daily_goal_messenger = DailyGoalMessenger.get_instance(sm.GetService('publicGatewaySvc')) 30 self._fetching_active = False 31 self._active_goals = {} 32 self._is_active_goals_dirty = True 33 self._fetching_unclaimed = False 34 self._unclaimed_goals = {} 35 self._is_unclaimed_dirty = True 36 self._unclaimed_goal_ids = set() 37 self._has_tried_fetching_any_unclaimed_ids = False 38 self._abort_fetching = False 39 self._connect_to_messenger_signals() 40 AssetHoldingController.get_instance() 41 sm.RegisterNotify(self) 42 43 def __del__(self): 44 self._disconnect_from_messenger_signals() 45 46 @classmethod 47 def get_instance(cls): 48 if not cls._instance: 49 cls._instance = DailyGoalsController() 50 return cls._instance 51 52 def _connect_to_messenger_signals(self): 53 messenger_signals.on_current_goals_received_internal.connect(self._on_current_goals_received) 54 messenger_signals.on_goal_completed_internal.connect(self._on_goal_completed) 55 messenger_signals.on_goal_progressed_internal.connect(self._on_goal_progressed) 56 messenger_signals.on_goal_redeemed_internal.connect(self._on_goal_redeemed) 57 dailyGoalSignals.on_availability_changed.connect(self._on_availability_changed) 58 on_entitlement_redeemed.connect(self._on_entitlement_redeemed) 59 60 def _disconnect_from_messenger_signals(self): 61 messenger_signals.on_current_goals_received_internal.disconnect(self._on_current_goals_received) 62 messenger_signals.on_goal_completed_internal.disconnect(self._on_goal_completed) 63 messenger_signals.on_goal_progressed_internal.disconnect(self._on_goal_progressed) 64 messenger_signals.on_goal_redeemed_internal.disconnect(self._on_goal_redeemed) 65 dailyGoalSignals.on_availability_changed.disconnect(self._on_availability_changed) 66 on_entitlement_redeemed.disconnect(self._on_entitlement_redeemed) 67 68 def flush_cache(self): 69 if self._fetching_active or self._fetching_unclaimed: 70 self._abort_fetching = True 71 self._fetching_active = False 72 self._active_goals = {} 73 self._is_active_goals_dirty = True 74 self._unclaimed_goals = {} 75 self._is_unclaimed_dirty = True 76 self._fetching_unclaimed = False 77 self._unclaimed_goal_ids.clear() 78 self._has_tried_fetching_any_unclaimed_ids = False 79 dailyGoalSignals.on_cache_invalidated() 80 81 def get_all_goals(self): 82 all_goals = self._get_active_goals() 83 if self._is_unclaimed_dirty: 84 uthread2.start_tasklet(self._fetch_unclaimed_history_ids) 85 all_goals.extend([ goal for goal in self._unclaimed_goals.values() if goal.has_data() ]) 86 return all_goals 87 88 def get_daily_goal_ids_of_the_day(self): 89 result = [] 90 for goal in self._active_goals.itervalues(): 91 if goal.get_category() == DailyGoalCategory.CATEGORY_DAILY: 92 result.append(goal.get_id()) 93 94 return result 95 96 def get_daily_bonus_goal_id_of_the_day(self): 97 for goal in self._active_goals.itervalues(): 98 if goal.get_category() == DailyGoalCategory.CATEGORY_DAILY_BONUS: 99 return goal.get_id() 100 101 def get_monthly_milestone_goal_ids(self): 102 result = [] 103 for goal in self._active_goals.itervalues(): 104 if goal.get_category() == DailyGoalCategory.CATEGORY_MONTHLY_BONUS: 105 result.append(goal.get_id()) 106 107 return result 108 109 def has_unclaimed_rewards(self): 110 if not bool(self._unclaimed_goal_ids) and not self._has_tried_fetching_any_unclaimed_ids and self._is_unclaimed_dirty and not self._fetching_unclaimed and are_daily_goals_enabled(): 111 self._has_tried_fetching_any_unclaimed_ids = True 112 uthread2.start_tasklet(self._fetch_unclaimed_page, None) 113 return bool(self._unclaimed_goal_ids) 114 115 def redeem_reward(self, goal_id, redeem_to_current_location = False): 116 try: 117 self._daily_goal_messenger.redeem_reward(goal_id, redeem_to_current_location) 118 except GoalNotRedeemableToCurrentLocation: 119 try: 120 self._daily_goal_messenger.redeem_reward(goal_id, do_redeem_current_location=False) 121 except Exception as e: 122 logger.exception('Daily Goals: Unable to redeem on retry for goal %s' % goal_id) 123 dailyGoalSignals.on_goal_payment_failed(goal_id) 124 125 except Exception as e: 126 logger.exception('Daily Goals: Failed to redeem goal with id %s' % goal_id) 127 dailyGoalSignals.on_goal_payment_failed(goal_id) 128 129 def pay_for_completion(self, goal_id): 130 try: 131 self._daily_goal_messenger.pay_for_completion(goal_id) 132 self._get_goal(goal_id).set_paid_completion(True) 133 dailyGoalSignals.on_pay_for_completion_successful(goal_id) 134 except GoalMessengerError as gme: 135 logger.exception('Daily Goals: Redeeming the goal returned status code %s' % gme.status_code) 136 dailyGoalSignals.on_pay_for_completion_failed(goal_id, gme.status_code) 137 except Exception as e: 138 logger.exception('Daily Goals: Failed to redeem goal with id %s' % goal_id) 139 dailyGoalSignals.on_pay_for_completion_failed(goal_id, None) 140 141 def get_completion_convenience_counter(self): 142 ret = 0 143 for goal in self._get_active_goals(): 144 if goal.paid_completion(): 145 ret += 1 146 147 return ret 148 149 @threadutils.threaded 150 def fetch_unclaimed_goals_data(self): 151 if not are_daily_goals_enabled(): 152 return 153 if self._is_unclaimed_dirty: 154 self._fetch_unclaimed_history_ids() 155 while self._fetching_unclaimed: 156 uthread2.Yield() 157 158 for goal in self._unclaimed_goals.values(): 159 self._fetch_missing_goal_data(goal) 160 161 def _get_cached_goal(self, goal_id): 162 if goal_id in self._active_goals: 163 return self._active_goals[goal_id] 164 if goal_id in self._unclaimed_goals: 165 return self._unclaimed_goals[goal_id] 166 167 def _get_goal(self, goal_id): 168 goal = self._get_cached_goal(goal_id) 169 if goal: 170 return goal 171 data = self._fetch_goal(goal_id) 172 if not data: 173 return None 174 goal = DailyGoal(goal_id) 175 goal.set_data(data) 176 if goal.is_expired: 177 self._unclaimed_goals[goal_id] = goal 178 else: 179 self._active_goals[goal_id] = goal 180 return goal 181 182 def _get_active_goals(self): 183 self._fetch_active_goals() 184 return self._active_goals.values() 185 186 def _fetch_active_goals(self): 187 if self._fetching_active or not self._is_active_goals_dirty or not are_daily_goals_enabled(): 188 return 189 self._fetching_active = True 190 try: 191 data = self._daily_goal_messenger.get_current_goals() 192 except Exception as e: 193 self._fetching_active = False 194 self._abort_fetching = False 195 raise e 196 197 for goal in data: 198 goal_id = goal.get('goal_id', None) 199 if goal and goal_id: 200 daily_goal = DailyGoal(goal_id) 201 daily_goal.set_data(goal) 202 self._active_goals[goal_id] = daily_goal 203 if goal_id in self._unclaimed_goals: 204 del self._unclaimed_goals[goal_id] 205 dailyGoalSignals.on_goal_data_fetched(daily_goal) 206 if goal_id not in self._unclaimed_goal_ids and daily_goal.has_unclaimed_rewards(): 207 self._unclaimed_goal_ids.add(goal_id) 208 dailyGoalSignals.on_unclaimed_goal_ids_changed() 209 210 self._fetching_active = False 211 self._is_active_goals_dirty = False 212 self._abort_fetching = False 213 214 def _fetch_unclaimed_history_ids(self): 215 if self._fetching_unclaimed or not self._is_unclaimed_dirty or not are_daily_goals_enabled(): 216 return 217 self._fetching_unclaimed = True 218 next_token = None 219 has_exception = False 220 while next_token != '': 221 try: 222 next_token = self._fetch_unclaimed_page(token=next_token) 223 except Exception as e: 224 logger.exception('Daily Goals: Unable to fetch goal ids with token %s, %s' % (next_token, str(e))) 225 has_exception = True 226 break 227 228 if self._abort_fetching: 229 break 230 231 self._fetching_unclaimed = False 232 self._is_unclaimed_dirty = has_exception 233 self._abort_fetching = False 234 dailyGoalSignals.on_unclaimed_goals_fetched() 235 236 def _fetch_unclaimed_page(self, token): 237 if token is None: 238 token = '' 239 self._has_tried_fetching_any_unclaimed_ids = True 240 next_token, goal_ids = self._daily_goal_messenger.get_all_with_rewards_page(token=token, size=BATCH_SIZE) 241 if self._abort_fetching: 242 self._abort_fetching = False 243 self._fetching_unclaimed = False 244 return 245 for goal_id in goal_ids: 246 if goal_id in self._active_goals: 247 continue 248 if goal_id not in self._unclaimed_goals: 249 self._unclaimed_goals[goal_id] = DailyGoal(goal_id) 250 251 if bool(goal_ids): 252 self._unclaimed_goal_ids.update(goal_ids) 253 dailyGoalSignals.on_unclaimed_goal_ids_changed() 254 return next_token 255 256 @threadutils.threaded 257 def _fetch_missing_goal_data(self, goal): 258 if not goal or goal.fetching or goal.has_data(): 259 return 260 goal.fetching = True 261 data = self._fetch_goal(goal.get_id()) 262 goal.fetching = False 263 if data is not None: 264 goal.set_data(data) 265 dailyGoalSignals.on_goal_data_fetched(goal) 266 267 def _error_cleanup(self, log_text, exception): 268 logger.exception('%s: %s' % log_text, str(exception)) 269 self._fetching_unclaimed = False 270 self._abort_fetching = False 271 272 def _fetch_goal(self, goal_id): 273 try: 274 goal_data = self._daily_goal_messenger.get_goal(goal_id) 275 except Exception as e: 276 logger.exception('Unable to fetch goal: %s' % str(e)) 277 return None 278 279 return goal_data 280 281 def _on_current_goals_received(self): 282 self.flush_cache() 283 284 @threadutils.threaded 285 def _on_goal_completed(self, goal_id): 286 goal = self._get_goal(goal_id) 287 if not goal: 288 return 289 self._unclaimed_goal_ids.add(goal_id) 290 desired_progress = goal.get_target_progress() 291 goal.set_current_progress(desired_progress) 292 dailyGoalSignals.on_completed(goal_id) 293 if goal.get_category() != DailyGoalCategory.CATEGORY_MONTHLY_BONUS: 294 self._dispatch_goal_completed_notification(goal_id, goal.get_name()) 295 elif not goal.is_last_milestone: 296 self._dispatch_milestone_completed_notification() 297 elif not get_clone_grade_svc().is_omega_with_grace_period(): 298 self._dispatch_track_completed_notification() 299 300 def _on_goal_progressed(self, goal_id, progress): 301 if goal_id not in self._active_goals: 302 return 303 previous_progress = self._active_goals[goal_id].get_current_progress() 304 self._active_goals[goal_id].set_current_progress(progress) 305 dailyGoalSignals.on_progress_changed(goal_id, progress, previous_progress) 306 307 def _is_cached_active_or_unclaimed(self, goal_id): 308 return goal_id in self._active_goals or goal_id in self._unclaimed_goals 309 310 def _on_goal_redeemed(self, goal_id): 311 if not self._is_cached_active_or_unclaimed(goal_id): 312 return 313 goal = self._get_goal(goal_id) 314 if not goal: 315 return 316 goal.mark_as_redeemed() 317 dailyGoalSignals.on_goal_payment_redeemed(goal_id) 318 dailyGoalSignals.on_goal_payment_complete(goal_id) 319 self._unclaimed_goal_ids.discard(goal_id) 320 dailyGoalSignals.on_unclaimed_goal_ids_changed() 321 322 def _on_entitlement_redeemed(self, goal_id, _): 323 self._on_goal_redeemed(goal_id) 324 325 def _on_availability_changed(self, *args, **kwargs): 326 self.flush_cache() 327 328 def _dispatch_goal_completed_notification(self, goal_id, goal_name): 329 sm.GetService('notificationSvc').MakeAndScatterNotification(notificationconst.notificationTypeDailyGoalCompleted, data={'goalName': goal_name, 330 'goalID': goal_id}) 331 332 def _dispatch_milestone_completed_notification(self): 333 sm.GetService('notificationSvc').MakeAndScatterNotification(notificationconst.notificationTypeDailyMilestoneCompleted, data={'isOmega': get_clone_grade_svc().is_omega_with_grace_period()}) 334 335 def _dispatch_track_completed_notification(self): 336 sm.GetService('notificationSvc').MakeAndScatterNotification(notificationconst.notificationTypeDailyRewardTrackCompleted, data={}) 337 338 def admin_set_progress(self, goal_id, progress_to_set): 339 if session.role & ROLE_QA: 340 goal = self._get_goal(goal_id) 341 if goal: 342 progress_to_add = progress_to_set - goal.get_current_progress() 343 progress_to_add = max(0, progress_to_add) 344 progress_to_add = min(progress_to_add, goal.get_target_progress() - goal.get_current_progress()) 345 if progress_to_add > 0: 346 self._daily_goal_messenger.set_goal_progress(goal_id, session.charid, progress_to_add) 347 348 def OnCharacterSelected(self, *args, **kwargs): 349 self.flush_cache() 350 351 352 def __SakeReloadHook(): 353 try: 354 DailyGoalsController.get_instance().flush_cache() 355 except Exception: 356 import log 357 log.LogException()