industrySvc.py (16544B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\industry\industrySvc.py 2 import weakref 3 from collections import defaultdict 4 import industry 5 import uthread 6 import telemetry 7 import blue 8 import gametime 9 import evetypes 10 import structures 11 from carbon.common.script.sys.service import Service 12 from carbon.common.script.sys.serviceConst import SERVICE_RUNNING 13 from carbon.common.script.util.format import DateToBlue 14 from eve.client.script.ui.shared.neocom.wallet import walletUtil 15 from eve.common.lib import appConst 16 from eve.common.script.util import industryCommon 17 from eve.common.script.sys.idCheckers import IsStation, IsStructure, IsTriglavianSystem 18 from eveaccounting import get_accounting_key_for_name 19 from eveexceptions import UserError 20 from evestations.standingsrestriction import get_station_standings_restriction 21 from uthread2 import call_after_wallclocktime_delay 22 23 class IndustryService(Service): 24 __guid__ = 'svc.industrySvc' 25 __servicename__ = 'Industry' 26 __displayname__ = 'Industry Service' 27 __dependencies__ = ['blueprintSvc', 'facilitySvc', 'clientPathfinderService'] 28 __notifyevents__ = ['OnIndustryMaterials', 29 'OnIndustryJob', 30 'OnSessionChanged', 31 'OnAccountChange', 32 'OnSkillsChanged', 33 'OnSessionReset'] 34 35 def __init__(self): 36 super(IndustryService, self).__init__() 37 self.monitoring = weakref.ref(set()) 38 self.installed = weakref.WeakValueDictionary() 39 self._jobs = {} 40 self._ownerJobs = defaultdict(dict) 41 self._ownerJobsFetchedCompleted = set() 42 self.slots = None 43 self.slotworker = None 44 45 def Run(self, *args, **kwargs): 46 uthread.new(self._PollJobCompletion) 47 Service.Run(self, *args, **kwargs) 48 49 def Initialize(self): 50 self.monitoring = weakref.ref(set()) 51 self.installed = weakref.WeakValueDictionary() 52 self.slots = None 53 self.slotworker = None 54 55 def GetJobByID(self, jobID, useCached = False): 56 if useCached and jobID in self._jobs: 57 return self._jobs[jobID] 58 return self._JobInstance(sm.RemoteSvc('industryManager').GetJob(jobID), fetchBlueprint=True) 59 60 @telemetry.ZONE_METHOD 61 def GetOwnerJobs(self, ownerID, includeCompleted = False, useCached = False): 62 if useCached and ownerID in self._ownerJobs: 63 if not includeCompleted: 64 return [ job for job in self._ownerJobs[ownerID].values() if job.status < industry.STATUS_COMPLETED ] 65 if ownerID in self._ownerJobsFetchedCompleted: 66 return self._ownerJobs[ownerID].values() 67 jobs = [] 68 locations = set() 69 self._ownerJobs[ownerID].clear() 70 self._ownerJobsFetchedCompleted.discard(ownerID) 71 for data in sm.RemoteSvc('industryManager').GetJobsByOwner(ownerID, includeCompleted): 72 job = self._JobInstance(data, fetchBlueprint=False) 73 self._ownerJobs[ownerID][job.jobID] = job 74 jobs.append(job) 75 if not job.completed: 76 locations.add(job.facilityID) 77 78 if includeCompleted: 79 self._ownerJobsFetchedCompleted.add(ownerID) 80 cfg.evelocations.Prime(locations) 81 return jobs 82 83 def GetCharacterJobs(self, includeCompleted = False, useCached = False): 84 return self.GetOwnerJobs(session.charid, includeCompleted, useCached) 85 86 def GetCorporationJobs(self, includeCompleted = False, useCached = False): 87 return self.GetOwnerJobs(session.corpid, includeCompleted, useCached) 88 89 @telemetry.ZONE_METHOD 90 def CreateJob(self, blueprint, activityID, facilityID, runs = 1): 91 job = industry.Job(blueprint, activityID) 92 job.runs = runs 93 job.status = industry.STATUS_UNSUBMITTED 94 job.extras = industryCommon.GetOptionalMaterials(job) 95 job.prices = industryCommon.JobPrices(activityID) 96 industryCommon.AttachSessionToJob(job, session) 97 self._UpdateSkills(job) 98 self._UpdateSlots() 99 self._UpdateModifiers(job) 100 self._UpdateDistance(job) 101 self._UpdateAccounts(job) 102 job.on_facility.connect(self.LoadLocations) 103 job.on_delete.connect(self.DisconnectJob) 104 job.on_input_location.connect(self.ConnectJob) 105 job.on_validate.connect(self._ValidateJob) 106 self._ApplyJobSettings(job) 107 job.facility = self.facilitySvc.GetFreshFacility(facilityID) 108 return job 109 110 def RecreateJob(self, existing): 111 try: 112 blueprint = self.blueprintSvc.GetBlueprintItem(existing.blueprintID) 113 if blueprint.ownerID != existing.ownerID: 114 raise UserError 115 except UserError: 116 blueprint = self.blueprintSvc.GetBlueprintType(existing.blueprintTypeID, not existing.blueprint.original) 117 118 job = self.CreateJob(blueprint, existing.activityID, existing.facilityID) 119 job.outputLocation = industryCommon.MatchLocation(job, existing.outputLocationID, existing.outputFlagID, checkCapacity=True) 120 job.productTypeID = existing.productTypeID 121 job.runs = existing.runs 122 job.licensedRuns = existing.licensedRuns 123 for material in job.optional_materials: 124 material.select(None) 125 if getattr(existing, 'optionalTypeID', None) in material.all_types(): 126 material.select(existing.optionalTypeID) 127 if getattr(existing, 'optionalTypeID2', None) in material.all_types(): 128 material.select(existing.optionalTypeID2) 129 130 return job 131 132 def JobDataWithBlueprint(self, existing): 133 job = self._JobInstance(existing.data, fetchBlueprint=True) 134 job.extras = industryCommon.GetOptionalMaterials(job) 135 return job 136 137 def InstallJob(self, job): 138 try: 139 return sm.RemoteSvc('industryManager').InstallJob(job.dump()) 140 except UserError: 141 self.facilitySvc.Reload(job.facilityID) 142 raise 143 144 @staticmethod 145 def CompleteJob(jobID, solarSystemID): 146 sm.RemoteSvc('industryManager').CompleteJob(int(jobID), solarSystemID) 147 148 @staticmethod 149 def CompleteJobs(jobs): 150 sm.RemoteSvc('industryManager').CompleteManyJobs([ (jobID, solarSystemID) for jobID, solarSystemID in jobs ]) 151 152 @staticmethod 153 def CancelJob(jobID, solarSystemID): 154 sm.RemoteSvc('industryManager').CancelJob(int(jobID), solarSystemID) 155 156 def _ValidateJob(self, job): 157 if not job.facility: 158 return 159 if IsStation(job.facility.facilityID): 160 standing_restrictions = get_station_standings_restriction(appConst.stationServiceFactory, job.facility.facilityID, job.ownerID) 161 if standing_restrictions: 162 job.add_error(industry.Error.STANDINGS_RESTRICTION, standing_restrictions) 163 if IsStructure(evetypes.GetCategoryID(job.facility.typeID)) and IsTriglavianSystem(job.facility.solarSystemID): 164 if job.structure_service_id in (structures.SERVICE_MANUFACTURING_CAPITAL, structures.SERVICE_MANUFACTURING_SUPERCAPITAL): 165 job.add_error(industry.Error.FACILITY_TYPE, job.blueprint.blueprintTypeID) 166 167 def _PollJobCompletion(self): 168 while self.state == SERVICE_RUNNING: 169 uthread.new(self._PollJobCompletionThreaded) 170 blue.pyos.synchro.SleepWallclock(1000) 171 172 def _PollJobCompletionThreaded(self): 173 for job in self.installed.values(): 174 if job.status == industry.STATUS_INSTALLED and DateToBlue(job.endDate) < blue.os.GetWallclockTime(): 175 job.status = industry.STATUS_READY 176 sm.ScatterEvent('OnIndustryJob', job.jobID, job.ownerID, job.blueprintID, job.installerID, job.status, None) 177 178 def _JobInstance(self, data, fetchBlueprint = False): 179 if fetchBlueprint: 180 blueprint = self.blueprintSvc.GetBlueprint(data.blueprintID, data.blueprintTypeID) 181 else: 182 blueprint = self.blueprintSvc.GetBlueprintType(data.blueprintTypeID, data.blueprintCopy) 183 job = industryCommon.JobData(data, blueprint) 184 if data.facilityID: 185 job.facility = self.facilitySvc.GetFreshFacility(data.facilityID) 186 self._UpdateSkills(job) 187 self._UpdateSlots() 188 self._UpdateModifiers(job) 189 self._UpdateDistance(job) 190 if job.status == industry.STATUS_INSTALLED: 191 self.installed[job.jobID] = job 192 else: 193 self.installed.pop(job.jobID, None) 194 self._jobs[job.jobID] = job 195 return job 196 197 @telemetry.ZONE_METHOD 198 def _UpdateModifiers(self, job): 199 if job: 200 job.modifiers = industryCommon.GetJobModifiers(job, session.charid) 201 202 @telemetry.ZONE_METHOD 203 def _UpdateDistance(self, job): 204 if job and isinstance(job, industry.JobData): 205 job._distance = self.clientPathfinderService.GetJumpCountFromCurrent(job.solarSystemID) 206 207 @telemetry.ZONE_METHOD 208 def _UpdateSkills(self, job): 209 if job: 210 skills = {} 211 mySkills = sm.GetService('skills').GetSkill 212 for typeID in [ skill.typeID for skill in job.all_skills ]: 213 skillInfo = mySkills(typeID) 214 skills[typeID] = skillInfo.effectiveSkillLevel if skillInfo else 0 215 216 job.skills = skills 217 218 def _UpdateSlots(self): 219 callinterval = 3 220 callthread = self.slotworker 221 startTime = getattr(callthread.tasklet, 'startTime', gametime.GetWallclockTimeNow()) if callthread else gametime.GetWallclockTimeNow() 222 delay = 1 if callthread is None else callinterval 223 if callthread is None or not callthread.IsAlive() or gametime.GetSecondsSinceWallclockTime(startTime) > delay: 224 self.slotworker = call_after_wallclocktime_delay(self._UpdateSlots_thread, delay) 225 226 def _UpdateSlots_thread(self): 227 self.slots = sm.RemoteSvc('industryManager').GetJobCounts(session.charid) 228 self.slotworker = None 229 sm.ScatterEvent('OnIndustrySlotsUpdated') 230 231 @telemetry.ZONE_METHOD 232 def _UpdateAccounts(self, job, ownerID = None, account = None, balance = None): 233 if job: 234 accounts = {(session.charid, appConst.accountingKeyCash): sm.GetService('wallet').GetWealth()} 235 if session.corpAccountKey and walletUtil.HaveAccessToCorpWalletDivision(session.corpAccountKey): 236 if ownerID and account and balance and session.corpid == ownerID and session.corpAccountKey == account: 237 accounts[session.corpid, session.corpAccountKey] = balance 238 else: 239 accounts[session.corpid, session.corpAccountKey] = sm.GetService('wallet').GetCorpWealthCached1Min(session.corpAccountKey) 240 job.accounts = accounts 241 if job.account not in job.accounts: 242 for accountOwner, accountKey in job.accounts.keys(): 243 if job.ownerID == accountOwner: 244 job.account = (accountOwner, accountKey) 245 return 246 247 job.account = job.accounts.keys()[0] 248 249 def OnSessionChanged(self, isRemote, session, change): 250 industryCommon.AttachSessionToJob(self.monitoring(), session) 251 if 'corpAccountKey' in change: 252 self._UpdateAccounts(self.monitoring()) 253 if 'corprole' in change: 254 self._UpdateAccounts(self.monitoring()) 255 self.LoadLocations(self.monitoring()) 256 257 def OnSessionReset(self): 258 self.Initialize() 259 260 def OnAccountChange(self, accountKey, ownerID, balance): 261 keyID = get_accounting_key_for_name(accountKey) 262 self._UpdateAccounts(self.monitoring(), ownerID, keyID, balance) 263 264 def OnSkillsChanged(self, *args): 265 self._UpdateSkills(self.monitoring()) 266 self._UpdateModifiers(self.monitoring()) 267 268 @telemetry.ZONE_METHOD 269 def OnIndustryJob(self, jobID, ownerID, blueprintID, installerID, status, successfulRuns): 270 if installerID == session.charid: 271 self._UpdateSlots() 272 if jobID in self._jobs: 273 self._jobs[jobID].status = status 274 self._jobs[jobID].successfulRuns = successfulRuns 275 if ownerID in self._ownerJobs and jobID not in self._ownerJobs[ownerID]: 276 self._ownerJobs.pop(ownerID) 277 self._ownerJobsFetchedCompleted.discard(ownerID) 278 sm.ScatterEvent('OnIndustryJobClient', jobID=jobID, ownerID=ownerID, blueprintID=blueprintID, installerID=installerID, status=status, successfulRuns=successfulRuns) 279 280 @telemetry.ZONE_METHOD 281 def ConnectJob(self, job): 282 self.monitoring = weakref.ref(job) 283 job.monitorID, job.available = sm.RemoteSvc('industryMonitor').ConnectJob(job.dump()) 284 285 @telemetry.ZONE_METHOD 286 def DisconnectJob(self, job): 287 if job is None or self.monitoring() == job: 288 sm.RemoteSvc('industryMonitor').DisconnectJob(job.monitorID if job else None) 289 290 @telemetry.ZONE_METHOD 291 def LoadLocations(self, job): 292 if job: 293 job.locations = self.facilitySvc.GetFacilityLocations(job.facilityID, job.ownerID) 294 if len(job.locations): 295 self._ApplyJobSettings(job) 296 297 def OnIndustryMaterials(self, jobID, materials): 298 job = self.monitoring() 299 if job and hasattr(job, 'monitorID') and job.monitorID == jobID: 300 job.available = materials 301 302 def _UpdateJobSettings(self, job): 303 settings.char.ui.Set('industry_b:%s_a:%s_runs' % (job.blueprint.blueprintTypeID, job.activityID), job.runs) 304 settings.char.ui.Set('industry_b:%s_a:%s_productTypeID' % (job.blueprint.blueprintTypeID, job.activityID), job.productTypeID) 305 settings.char.ui.Set('industry_b:%s_a:%s_licensedRuns' % (job.blueprint.blueprintTypeID, job.activityID), job.licensedRuns) 306 settings.char.ui.Set('industry_account:%s' % (job.ownerID,), job.account) 307 if job.inputLocation is not None and job.facility is not None: 308 settings.char.ui.Set('industry_b:%s_a:%s_f:%s_input' % (job.blueprint.blueprintTypeID, job.activityID, job.facility.facilityID), (job.inputLocation.itemID, job.inputLocation.flagID)) 309 if job.outputLocation is not None and job.facility is not None: 310 settings.char.ui.Set('industry_b:%s_a:%s_f:%s_output' % (job.blueprint.blueprintTypeID, job.activityID, job.facility.facilityID), (job.outputLocation.itemID, job.outputLocation.flagID)) 311 if len(job.optional_materials): 312 settings.char.ui.Set('industry_b:%s_a:%s_materials' % (job.blueprint.blueprintTypeID, job.activityID), list([ material.typeID for material in job.optional_materials if material.typeID ])) 313 314 def _ApplyJobSettings(self, job): 315 job.runs = settings.char.ui.Get('industry_b:%s_a:%s_runs' % (job.blueprint.blueprintTypeID, job.activityID), 1) 316 job.productTypeID = settings.char.ui.Get('industry_b:%s_a:%s_productTypeID' % (job.blueprint.blueprintTypeID, job.activityID), None) 317 job.licensedRuns = min(job.maxLicensedRuns, settings.char.ui.Get('industry_b:%s_a:%s_licensedRuns' % (job.blueprint.blueprintTypeID, job.activityID), job.maxLicensedRuns)) 318 account = settings.char.ui.Get('industry_account:%s' % (job.ownerID,), job.account) 319 job.account = account if account in job.accounts else job.account 320 if job.facility: 321 inputLocation = industryCommon.MatchLocation(job, *settings.char.ui.Get('industry_b:%s_a:%s_f:%s_input' % (job.blueprint.blueprintTypeID, job.activityID, job.facility.facilityID), (job.blueprint.location.itemID, job.blueprint.location.flagID))) 322 if inputLocation != job.inputLocation: 323 job.inputLocation = inputLocation 324 outputLocation = industryCommon.MatchLocation(job, checkCapacity=True, *settings.char.ui.Get('industry_b:%s_a:%s_f:%s_output' % (job.blueprint.blueprintTypeID, job.activityID, job.facility.facilityID), (job.blueprint.location.itemID, job.blueprint.location.flagID))) 325 if outputLocation != job.outputLocation: 326 job.outputLocation = outputLocation 327 materials = settings.char.ui.Get('industry_b:%s_a:%s_materials' % (job.blueprint.blueprintTypeID, job.activityID), []) 328 for material in job.materials: 329 for option in material.options: 330 if option.typeID in materials: 331 material.select(option) 332 333 job.on_updated.connect(self._UpdateJobSettings) 334 335 def GetJobCountForActivity(self, activityID): 336 if self.slots is None: 337 self._UpdateSlots() 338 return 339 return self.slots.get(activityID, 0)