skill_training.py (9574B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\characterskills\skill_training.py 2 from contextlib import contextmanager 3 from characterskills.util import GetSkillPointsPerMinute, GetSPForLevelRaw 4 import gametime 5 from carbon.common.lib.const import MIN 6 7 class SkillAttributeMissingError(Exception): 8 pass 9 10 11 class CharacterSkillInterface(object): 12 13 def get_primary_attribute_id(self, skill_type_id): 14 raise NotImplementedError() 15 16 def get_secondary_attribute_id(self, skill_type_id): 17 raise NotImplementedError() 18 19 def get_skill_rank(self, skill_type_id): 20 raise NotImplementedError() 21 22 def get_skill_level(self, skill_type_id): 23 raise NotImplementedError() 24 25 def get_character_attribute_at_time(self, attribute_id, time_offset): 26 raise NotImplementedError() 27 28 def get_skill_points(self, skill_type_id): 29 raise NotImplementedError() 30 31 def get_skill_accelerator_boosters(self): 32 raise NotImplementedError() 33 34 35 class SkillLevelAlreadyTrainedError(Exception): 36 pass 37 38 39 class SkillPointsTrainedPerMinuteError(Exception): 40 pass 41 42 43 class SkillTrainingTimeCalculator(object): 44 45 def __init__(self, character_skill_interface): 46 self._character_skill_interface = character_skill_interface 47 self._current_time_function = None 48 self.reset_current_time_function() 49 50 def _get_primary_attribute_value_at_time(self, skill_type_id, time_offset): 51 attribute_id = self._character_skill_interface.get_primary_attribute_id(skill_type_id) 52 if not attribute_id: 53 raise SkillAttributeMissingError("Skill %s is missing it's primary attribute") 54 return self._character_skill_interface.get_character_attribute_at_time(attribute_id, time_offset) 55 56 def _get_secondary_attribute_value_at_time(self, skill_type_id, time_offset): 57 attribute_id = self._character_skill_interface.get_secondary_attribute_id(skill_type_id) 58 if not attribute_id: 59 raise SkillAttributeMissingError("Skill %s is missing it's secondary attribute") 60 return self._character_skill_interface.get_character_attribute_at_time(attribute_id, time_offset) 61 62 def _get_starting_skill_points(self, skill_level, skill_rank, skill_type_id, to_level): 63 if skill_level == to_level - 1: 64 from_skillpoints = self._character_skill_interface.get_skill_points(skill_type_id) 65 else: 66 from_skillpoints = GetSPForLevelRaw(skill_rank, to_level - 1) 67 return from_skillpoints 68 69 def _get_time_offset(self, training_start_time): 70 time_offset = 0 71 if training_start_time: 72 time_offset = training_start_time - self._get_current_time() 73 return time_offset 74 75 def _get_current_time(self): 76 return self._current_time_function() 77 78 def _get_skill_points_per_minute_at(self, skill_type_id, time_offset): 79 primary_attribute_value = self._get_primary_attribute_value_at_time(skill_type_id, time_offset) 80 secondary_attribute_value = self._get_secondary_attribute_value_at_time(skill_type_id, time_offset) 81 sp_per_minute = GetSkillPointsPerMinute(primary_attribute_value, secondary_attribute_value) 82 if sp_per_minute <= 0: 83 raise SkillPointsTrainedPerMinuteError('Skill points trained per minute is %s which suggests bad character attribute values. Skill type=%s, primary attribute=s, secondary attribute=%s.', sp_per_minute, skill_type_id, primary_attribute_value, secondary_attribute_value) 84 return sp_per_minute 85 86 def get_remaining_skill_points(self, skill_type_id, to_level): 87 skill_rank = self._character_skill_interface.get_skill_rank(skill_type_id) 88 skill_level = self._character_skill_interface.get_skill_level(skill_type_id) 89 from_skillpoints = self._get_starting_skill_points(skill_level, skill_rank, skill_type_id, to_level) 90 to_skillpoints = GetSPForLevelRaw(skill_rank, to_level) 91 remaining_skillpoints = to_skillpoints - from_skillpoints 92 return remaining_skillpoints 93 94 def _get_remaining_skill_points_given_existing_skill_points(self, skill_type_id, to_level, existing_skill_points): 95 skill_rank = self._character_skill_interface.get_skill_rank(skill_type_id) 96 to_skillpoints = GetSPForLevelRaw(skill_rank, to_level) 97 remaining_skillpoints = to_skillpoints - existing_skill_points 98 if remaining_skillpoints <= 0: 99 raise SkillLevelAlreadyTrainedError('Skill type %s already trained to level %s. Has %s skill points while needing only %s to achieve the level', skill_type_id, to_level, existing_skill_points, to_skillpoints) 100 return remaining_skillpoints 101 102 def _get_booster_expiry_time_offsets(self, training_start_time): 103 boosters = self._character_skill_interface.get_skill_accelerator_boosters() 104 if not boosters: 105 return [] 106 expiry_times = boosters.get_expiry_times_after_time(training_start_time) 107 return [ expiry_time - training_start_time for expiry_time in expiry_times ] 108 109 def _get_training_time_given_skill_points_to_train(self, skill_type_id, skillpoints_to_train, training_start_time): 110 time_offset = self._get_time_offset(training_start_time) 111 skillpoints_per_minute = self._get_skill_points_per_minute_at(skill_type_id, time_offset) 112 accumulated_skillpoints = 0 113 accumulated_time = 0 114 for expiry_time_offset in self._get_booster_expiry_time_offsets(training_start_time): 115 time_left_until_expiry = float(expiry_time_offset - accumulated_time) 116 skillpoints_until_expiry = time_left_until_expiry / MIN * skillpoints_per_minute 117 if accumulated_skillpoints + skillpoints_until_expiry >= skillpoints_to_train: 118 break 119 accumulated_skillpoints += skillpoints_until_expiry 120 accumulated_time += time_left_until_expiry 121 skillpoints_per_minute = self._get_skill_points_per_minute_at(skill_type_id, time_offset + expiry_time_offset) 122 123 remaining_skillpoints = skillpoints_to_train - accumulated_skillpoints 124 total_time = accumulated_time + long(float(remaining_skillpoints) / float(skillpoints_per_minute) * MIN) 125 return total_time 126 127 def get_skill_points_and_time_to_train(self, skill_type_id, to_level, training_start_time): 128 skillpoints_to_train = self.get_remaining_skill_points(skill_type_id, to_level) 129 total_time = self._get_training_time_given_skill_points_to_train(skill_type_id, skillpoints_to_train, training_start_time) 130 return (skillpoints_to_train, total_time) 131 132 def get_training_time(self, skill_type_id, to_level, training_start_time = None): 133 skillpoints_to_train, training_time = self.get_skill_points_and_time_to_train(skill_type_id, to_level, training_start_time) 134 return training_time 135 136 def get_skill_points_and_time_to_train_given_existing_skill_points(self, skill_type_id, to_level, training_start_time, existing_skill_points): 137 skillpoints_to_train = self._get_remaining_skill_points_given_existing_skill_points(skill_type_id, to_level, existing_skill_points) 138 total_time = self._get_training_time_given_skill_points_to_train(skill_type_id, skillpoints_to_train, training_start_time) 139 return (skillpoints_to_train, total_time) 140 141 def get_skill_points_trained_at_sample_time(self, skill_type_id, training_start_time, sample_time): 142 starting_skillpoints = self._character_skill_interface.get_skill_points(skill_type_id) or 0 143 if training_start_time > sample_time: 144 return starting_skillpoints 145 time_offset = self._get_time_offset(training_start_time) 146 skillpoints_per_minute = self._get_skill_points_per_minute_at(skill_type_id, time_offset) 147 sample_offset = sample_time - training_start_time 148 time_left_until_sample = sample_offset 149 accumulated_skillpoints = 0 150 accumulated_time = 0 151 for expiry_time_offset in self._get_booster_expiry_time_offsets(training_start_time): 152 if expiry_time_offset >= sample_offset: 153 time_left_until_sample = sample_offset - accumulated_time 154 break 155 time_left_until_expiry = expiry_time_offset - accumulated_time 156 skillpoints_until_expiry = time_left_until_expiry / float(MIN) * skillpoints_per_minute 157 accumulated_skillpoints += skillpoints_until_expiry 158 accumulated_time += time_left_until_expiry 159 skillpoints_per_minute = self._get_skill_points_per_minute_at(skill_type_id, time_offset + expiry_time_offset) 160 161 accumulated_skillpoints += time_left_until_sample / float(MIN) * skillpoints_per_minute 162 return int(accumulated_skillpoints + starting_skillpoints) 163 164 def get_skill_accelerator_boosters(self): 165 return self._character_skill_interface.get_skill_accelerator_boosters() 166 167 def set_current_time_function(self, function): 168 self._current_time_function = function 169 170 def reset_current_time_function(self): 171 self.set_current_time_function(gametime.GetWallclockTime) 172 173 @contextmanager 174 def specific_current_time_context(self, time): 175 176 def get_current_time(): 177 return time 178 179 sab = self.get_skill_accelerator_boosters() 180 sab.set_current_time_function(get_current_time) 181 self.set_current_time_function(get_current_time) 182 try: 183 yield self 184 finally: 185 sab.reset_current_time_function() 186 self.reset_current_time_function()