contracts.py (46739B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\neocom\contracts\contracts.py 2 import blue 3 import carbonui.const as uiconst 4 import eve.common.script.util.notificationconst as notificationConst 5 import eveformat.client 6 import evetypes 7 import localization 8 import structures 9 import uthread 10 import utillib 11 from caching.memoize import Memoize 12 from carbon.common.script.sys.service import Service 13 from carbon.common.script.util.format import FmtDate 14 from player_messaging.client.ui_message import message_player, prompt_player_fsd_dialog 15 from eve.client.script.ui.shared.neocom.contracts.contractsDetailsWnd import ContractDetailsWindow 16 from eve.client.script.ui.shared.neocom.contracts.contractsWnd import ContractsWindow 17 from eve.client.script.ui.shared.neocom.contracts.createContractWnd import CreateContractWnd 18 from eve.client.script.ui.shared.neocom.contracts.ignoreListWnd import IgnoreListWindow 19 from eve.client.script.ui.shared.neocom.contracts.multiContractsItemExchangeWnd import MultiContractsItemExchangeWnd 20 from eve.client.script.ui.util import uix 21 from eve.client.script.util.contractutils import FmtISKWithDescription, GetMarketTypes 22 from eve.common.lib import appConst as const 23 from eve.common.script.sys import idCheckers 24 from eve.common.script.util.contractscommon import CONTYPE_AUCTIONANDITEMECHANGE, GetContractTitle, MAX_AMOUNT, MAX_NUM_CONTRACTS, MINUTE, NUM_CONTRACTS_BY_SKILL, NUM_CONTRACTS_BY_ADVSKILL, NUM_CONTRACTS_BY_SKILL_CORP, const_conCourierWarningVolume, CanRequestType 25 from eveexceptions import UserError 26 from eveformat.client.location import get_security_status 27 from eveservices.menu import GetMenuService 28 from inventorycommon.const import typeContracting, typeCorporationContracting, typeAdvancedContracting 29 from locks import TempLock 30 from menu import MenuLabel 31 from notifications.common.formatters.contractAssigned import ContractAssignedFormatter 32 from notifications.common.formatters.contractAttention import ContractNeedsAttentionFormatter 33 from playerprogression.contractprogressionnotifier import ContractProgressionNotifier 34 from security.client.securityColor import get_security_status_color 35 MAX_IGNORED = 1000 36 CACHE_TIME = 10 37 38 def GetSecurityClassText(securityClass): 39 txt = {const.securityClassZeroSec: '<color=0xffbb0000>%s</color>' % localization.GetByLabel('UI/Common/NullSec'), 40 const.securityClassLowSec: '<color=0xffbbbb00>%s</color>' % localization.GetByLabel('UI/Common/LowSec'), 41 const.securityClassHighSec: '<color=0xff00bb00>%s</color>' % localization.GetByLabel('UI/Common/HighSec'), 42 const.securityClassSafeSec: '<color=0xff0000bb>%s</color>' % localization.GetByLabel('UI/Common/SafeSec')}.get(securityClass, localization.GetByLabel('UI/Contracts/ContractsWindow/UnknownSystem')) 43 return txt 44 45 46 class ContractsSvc(Service): 47 __exportedcalls__ = {'GetContract': {}, 48 'AcceptContract': {}, 49 'DeleteContract': {}, 50 'CompleteContract': {}, 51 'FailContract': {}, 52 'RejectContract': {}, 53 'CreateContract': {}, 54 'PlaceBid': {}, 55 'FinishAuction': {}, 56 'GetMarketTypes': {}, 57 'NumOutstandingContracts': {}, 58 'GetSolarSystemIDForStationOrStructure': {}, 59 'GetItemsInContainer': {}, 60 'GetItemsInDockableLocation': {}, 61 'DeleteNotification': {}, 62 'CollectMyPageInfo': {}, 63 'ClearCache': {}, 64 'AddIgnore': {}, 65 'GetMessages': {}, 66 'DeliverCourierContractFromItemID': {}, 67 'FindCourierContractFromItemID': {}} 68 __guid__ = 'svc.contracts' 69 __notifyevents__ = ['ProcessSessionChange', 70 'OnContractAssigned', 71 'OnContractCompleted', 72 'OnContractAccepted', 73 'OnContractRejected', 74 'OnContractFailed', 75 'OnContractOutbid', 76 'OnContractBuyout', 77 'OnContractCreated', 78 'OnNotificationUiReady', 79 'OnSessionReset', 80 'OnShowContract'] 81 __servicename__ = 'Contracts' 82 __displayname__ = 'Contracts Service' 83 __dependencies__ = [] 84 __update_on_reload__ = 0 85 86 def __init__(self): 87 Service.__init__(self) 88 self.ReInitialize() 89 90 def ReInitialize(self): 91 self.detailsByRegionByTypeFlag = {} 92 self.contractID = None 93 self.markettypes = None 94 self.contracts = {} 95 self.myPageInfo = None 96 self.myExpiredContractList = None 97 self.contractsInProgress = {} 98 self.messages = [] 99 self.inited = False 100 self.contractTypeIDFinder = None 101 self.SetupContractProgressionNotifier() 102 103 def SetupContractProgressionNotifier(self): 104 self.contractProgressionNotifier = ContractProgressionNotifier(get_contract_escrow_function=self.GetMyContractEscrow) 105 self.SubscribeContractProgressWatcher = self.contractProgressionNotifier.register_subscriber 106 self.UnsubscribeContractProgressWatcher = self.contractProgressionNotifier.unregister_subscriber 107 108 def Run(self, memStream = None): 109 self.contractProxySvc = None 110 111 def GetContractProxySvc(self): 112 if self.contractProxySvc == None: 113 self.contractProxySvc = sm.ProxySvc('contractProxy') 114 return self.contractProxySvc 115 116 def Stop(self, memStream = None): 117 ContractsWindow.CloseIfOpen() 118 119 def Init(self): 120 if session.charid and not self.inited: 121 self.inited = True 122 123 def GetContractsInProgress(self): 124 return self.contractsInProgress 125 126 def GetContractsBookmarkMenu(self): 127 contractsMenu = [] 128 stationsPickup = set() 129 stationsDropoff = set() 130 for contractID, contract in self.contractsInProgress.iteritems(): 131 if contract[-1] < blue.os.GetWallclockTime(): 132 continue 133 if cfg.evelocations.Get(contract[0]).solarSystemID == session.solarsystemid2: 134 stationsPickup.add(contract[0]) 135 if cfg.evelocations.Get(contract[1]).solarSystemID == session.solarsystemid2: 136 stationsDropoff.add(contract[1]) 137 138 for stationID in stationsPickup: 139 pickupLabel = localization.GetByLabel('UI/Contracts/ContractsService/CourierPickup', station=stationID) 140 contractsMenu.append((pickupLabel, ('isDynamic', GetMenuService().CelestialMenu, (stationID,)))) 141 142 for stationID in stationsDropoff: 143 dropoffLabel = localization.GetByLabel('UI/Contracts/ContractsService/CourierDelivery', station=stationID) 144 contractsMenu.append((dropoffLabel, ('isDynamic', GetMenuService().CelestialMenu, (stationID,)))) 145 146 return contractsMenu 147 148 def FindRelated(self, typeID, groupID, categoryID, issuerID, locationID, endLocationID, contractType = None, reset = True, *args): 149 if contractType != const.conTypeCourier: 150 contractType = CONTYPE_AUCTIONANDITEMECHANGE 151 self.OpenSearchTab(typeID, groupID, categoryID, issuerID, locationID, endLocationID, const.conAvailPublic, contractType, reset) 152 153 def OpenAssignedToMe(self, contractType = CONTYPE_AUCTIONANDITEMECHANGE, isCorp = False): 154 self.Show() 155 wnd = ContractsWindow.GetIfOpen() 156 if wnd is not None: 157 wnd = ContractsWindow.Open() 158 blue.pyos.synchro.SleepWallclock(10) 159 wnd.tabGroup.SelectByIdx(2) 160 wnd.contractSearchContent.FindMyContracts(contractType, isCorp) 161 162 def GetRelatedMenu(self, contract, typeID, reset = True): 163 m = [] 164 startConstellationID = sm.GetService('map').GetItem(contract.startSolarSystemID).locationID 165 if typeID and contract.type != const.conTypeCourier: 166 m += [(MenuLabel('UI/Contracts/ContractsWindow/SameType'), self.FindRelated, (typeID, 167 None, 168 None, 169 None, 170 None, 171 None, 172 contract.type, 173 reset))] 174 m += [(MenuLabel('UI/Contracts/ContractsWindow/SameGroup'), self.FindRelated, (None, 175 evetypes.GetGroupID(typeID), 176 None, 177 None, 178 None, 179 None, 180 contract.type, 181 reset))] 182 m += [(MenuLabel('UI/Contracts/ContractsWindow/SameCategory'), self.FindRelated, (None, 183 None, 184 evetypes.GetCategoryID(typeID), 185 None, 186 None, 187 None, 188 contract.type, 189 reset))] 190 m += [None] 191 m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameIssuer'), self.FindRelated, (None, 192 None, 193 None, 194 contract.issuerCorpID if contract.forCorp else contract.issuerID, 195 None, 196 None, 197 contract.type, 198 reset))] 199 m += [None] 200 m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameSolarSystem'), self.FindRelated, (None, 201 None, 202 None, 203 None, 204 contract.startSolarSystemID, 205 None, 206 contract.type, 207 reset))] 208 m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameConstellation'), self.FindRelated, (None, 209 None, 210 None, 211 None, 212 startConstellationID, 213 None, 214 contract.type, 215 reset))] 216 m += [(MenuLabel('UI/Contracts/ContractsWindow/FromSameRegion'), self.FindRelated, (None, 217 None, 218 None, 219 None, 220 contract.startRegionID, 221 None, 222 contract.type, 223 reset))] 224 if contract.type == const.conTypeCourier: 225 endConstellationID = sm.GetService('map').GetItem(contract.endSolarSystemID).locationID 226 m += [None] 227 m += [(MenuLabel('UI/Contracts/ContractsWindow/ToSameSolarSystem'), self.FindRelated, (None, 228 None, 229 None, 230 None, 231 None, 232 contract.endSolarSystemID, 233 contract.type, 234 reset))] 235 m += [(MenuLabel('UI/Contracts/ContractsWindow/ToSameConstellation'), self.FindRelated, (None, 236 None, 237 None, 238 None, 239 None, 240 endConstellationID, 241 contract.type, 242 reset))] 243 m += [(MenuLabel('UI/Contracts/ContractsWindow/ToSameRegion'), self.FindRelated, (None, 244 None, 245 None, 246 None, 247 None, 248 contract.endRegionID, 249 contract.type, 250 reset))] 251 return m 252 253 def DispatchAssignedNotification(self, assignedToMe): 254 if len(assignedToMe) > 0: 255 firstContractID = assignedToMe[0] 256 data = ContractAssignedFormatter.MakeData(contractCountAssignedToMe=len(assignedToMe), firstContractID=firstContractID) 257 sm.GetService('notificationSvc').MakeAndScatterNotification(notificationConst.notificationTypeContractAssigned, data) 258 259 def DispatchAttentionNotification(self, needsAttention): 260 if len(needsAttention) > 0: 261 firstContractInfo = needsAttention[0] 262 data = ContractNeedsAttentionFormatter.MakeData(needsAttention=len(needsAttention), isForCorp=firstContractInfo[1], firstContractID=firstContractInfo.contractID) 263 sm.GetService('notificationSvc').MakeAndScatterNotification(notificationConst.notificationTypeContractNeedsAttention, data) 264 265 def DispatchAcceptedNotication(self, contractId): 266 sm.GetService('notificationSvc').MakeAndScatterNotification(notificationConst.notificationTypeContractAccepted, {'contractId': contractId}) 267 268 def OnNotificationUiReady(self): 269 uthread.new(self.NeocomBlink) 270 271 def NeocomBlink(self): 272 kv = self.GetContractProxySvc().GetLoginInfo() 273 ignoreList = set(settings.user.ui.Get('contracts_ignorelist', [])) 274 assignedToMe = [] 275 for contract in kv.assignedToMe: 276 if contract.issuerID not in ignoreList: 277 assignedToMe.append(contract.contractID) 278 279 self.DispatchAssignedNotification(assignedToMe) 280 self.DispatchAttentionNotification(kv.needsAttention) 281 if len(kv.inProgress): 282 ip = {} 283 for contract in kv.inProgress: 284 ip[contract.contractID] = [contract.startStationID, contract.endStationID, contract.expires] 285 286 self.contractsInProgress = ip 287 288 def ProcessSessionChange(self, isremote, sess, change): 289 if 'charid' in change and change['charid'][1] is not None: 290 self.GetContractProxySvc() 291 self.Init() 292 293 def OnSessionReset(self): 294 self.ReInitialize() 295 296 def _NotifyOfEscrowChange(self): 297 sm.ScatterEvent('OnContractEscrowChanged') 298 299 def OnContractCreated(self): 300 self._NotifyOfEscrowChange() 301 302 def OnContractAssigned(self, contractID): 303 contract = self.GetContract(contractID) 304 c = contract.contract 305 issuerID = [c.issuerID, c.issuerCorpID][c.forCorp] 306 ignoreList = settings.user.ui.Get('contracts_ignorelist', []) 307 if issuerID in ignoreList: 308 return 309 self.DispatchAssignedNotification([contractID]) 310 self.ContractNotify('UI/Contracts/ContractsService/ContractMessageAssigned', contractID) 311 312 def OnContractCompleted(self, contractID): 313 self.ContractNotify('UI/Contracts/ContractsService/ContractMessageCompleted', contractID) 314 self._NotifyOfEscrowChange() 315 316 def OnContractAccepted(self, contractID): 317 self.DispatchAcceptedNotication(contractID) 318 self._NotifyOfEscrowChange() 319 320 def OnContractRejected(self, contractID): 321 self.ContractNotify('UI/Contracts/ContractsService/ContractMessageRejected', contractID) 322 self._NotifyOfEscrowChange() 323 324 def OnContractFailed(self, contractID): 325 self.ContractNotify('UI/Contracts/ContractsService/ContractMessageFailed', contractID) 326 self._NotifyOfEscrowChange() 327 328 def OnContractOutbid(self, contractID): 329 self.ContractNotify('UI/Contracts/ContractsService/ContractMessageOutbid', contractID) 330 self._NotifyOfEscrowChange() 331 332 def OnContractBuyout(self, contractID): 333 self.ContractNotify('UI/Contracts/ContractsService/ContractMessageBuyout', contractID) 334 self._NotifyOfEscrowChange() 335 336 def ContractNotify(self, cerberusLabel, contractID): 337 MAX_NUM_MESSAGES = 5 338 contract = self.GetContract(contractID, force=False) 339 c = contract.contract 340 title = GetContractTitle(c, contract.items) 341 link = '<a href="contract:%d//%d">%s</a>' % (c.startSolarSystemID, contractID, title) 342 message = localization.GetByLabel(cerberusLabel, timeStamp=FmtDate(blue.os.GetWallclockTime(), 'ls'), contractLink=link) 343 self.messages.append(message) 344 if len(self.messages) > MAX_NUM_MESSAGES: 345 self.messages.pop(0) 346 self.Blink() 347 self.ClearCache() 348 349 def Blink(self): 350 sm.GetService('neocom').Blink('contracts', localization.GetByLabel('UI/Contracts/ContractsWindow/ContractsRequireAttention')) 351 352 def ShowAssignedTo(self, *args): 353 self.Show() 354 self.OpenAvailableTab(3, 1) 355 356 def Show(self, lookup = None, idx = None): 357 ContractsWindow.CloseIfOpen() 358 ContractsWindow.Open(lookup=lookup, idx=idx) 359 360 def GetRouteSecurityWarning(self, startSolarSystemID, endSolarSystemID): 361 securitySvc = sm.GetService('securitySvc') 362 path = sm.GetService('clientPathfinderService').GetAutopilotPathBetween(startSolarSystemID, endSolarSystemID) 363 txt = '' 364 if not path: 365 txt = '<color=0xffbb0000>%s</color>' % sm.GetService('clientPathfinderService').GetNoRouteFoundText(endSolarSystemID).upper() 366 else: 367 mySecurityClass = securitySvc.get_modified_security_class(session.solarsystemid2) 368 pathClasses = set() 369 for solarSystemID in path: 370 pathClasses.add(securitySvc.get_modified_security_class(solarSystemID)) 371 372 securityClassList = [] 373 for p in pathClasses: 374 if p != mySecurityClass: 375 securityClassList.append(GetSecurityClassText(p)) 376 377 securityClassString = localization.formatters.FormatGenericList(securityClassList) 378 if len(securityClassList) > 0: 379 txt = localization.GetByLabel('UI/Contracts/ContractsService/RouteWillTakeYouThrough', listOfSecurityRatings=securityClassString) 380 return txt 381 382 def ShowContract(self, contractID): 383 self.contractID = contractID 384 ContractDetailsWindow.CloseIfOpen() 385 ContractDetailsWindow.Open(contractID=contractID) 386 387 def ShowManyContracts(self, contractIDs): 388 MultiContractsItemExchangeWnd.CloseIfOpen() 389 MultiContractsItemExchangeWnd.Open(contractIDs=contractIDs) 390 391 def GetContract(self, contractID, force = False): 392 v = self.contracts.get(contractID, None) 393 diff = 0 394 if v: 395 diff = blue.os.GetWallclockTime() - v.time 396 if contractID not in self.contracts or force or diff > 5 * const.MIN and not force: 397 self.LogInfo('Fetching contract %s from the server' % contractID) 398 contract = self.GetContractProxySvc().GetContract(contractID) 399 v = utillib.KeyVal() 400 v.time = blue.os.GetWallclockTime() 401 v.contract = contract 402 self.contracts[contractID] = v 403 else: 404 self.LogInfo('Using client cache for contract %s' % contractID) 405 return self.contracts[contractID].contract 406 407 def AcceptContract(self, contractID, forCorp): 408 contract = self.GetContract(contractID, force=None) 409 c = contract.contract 410 if not contract: 411 raise UserError('ConContractNotFound') 412 if not self.CheckEndpointAccess(c.startStationID): 413 return 414 if c.endStationID and c.endStationID != c.startStationID and not self.CheckEndpointAccess(c.endStationID): 415 return 416 n = sm.GetService('clientPathfinderService').GetAutopilotJumpCount(session.solarsystemid2, c.startSolarSystemID) 417 n2 = 0 418 if c.endSolarSystemID: 419 n2 = sm.GetService('clientPathfinderService').GetAutopilotJumpCount(session.solarsystemid2, c.endSolarSystemID) 420 n = max(n, n2) 421 if n > 1000: 422 if prompt_player_fsd_dialog('ConConfirmNotReachable', {}, uiconst.YESNO) != uiconst.ID_YES: 423 return False 424 wallet = sm.GetService('corp').GetMyCorpAccountName() 425 args = {} 426 if forCorp: 427 args['youoryourcorp'] = localization.GetByLabel('UI/Contracts/ContractsService/YouOnBehalfOfYourCorp', corpName=cfg.eveowners.Get(session.corpid).name, wallet=wallet) 428 else: 429 args['youoryourcorp'] = localization.GetByLabel('UI/Common/You') 430 args['contractname'] = GetContractTitle(c, contract.items) 431 if c.type == const.conTypeAuction: 432 raise RuntimeError('You cannot accept an auction') 433 elif c.type == const.conTypeItemExchange: 434 msg = 'ConConfirmAcceptItemExchange' 435 reportGet = reportPay = '' 436 for item in contract.items: 437 if item.inCrate: 438 reportGet += '<t>%s.<br>' % cfg.FormatConvert(const.UE_TYPEIDANDQUANTITY, item.itemTypeID, max(1, item.quantity)) 439 else: 440 reportPay += '<t>%s.<br>' % cfg.FormatConvert(const.UE_TYPEIDANDQUANTITY, item.itemTypeID, max(1, item.quantity)) 441 442 if reportGet != '': 443 reportGet = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmItemsGet', items=reportGet) 444 if reportPay != '': 445 reportPay = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmAcceptItemsPay', items=reportPay) 446 if reportGet == '' and c.reward == 0 and (reportPay != '' or c.price > 0): 447 msg = 'ConConfirmAcceptItemExchangeGift' 448 if reportPay != '' and forCorp: 449 reportPay += localization.GetByLabel('UI/Contracts/ContractsWindow/ConfirmAcceptCorpItems') 450 args['itemsget'] = reportGet 451 args['itemspay'] = reportPay 452 payorgetmoney = '' 453 if c.price > 0: 454 payorgetmoney = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmAcceptPayMoney', numISK=FmtISKWithDescription(c.price)) 455 elif c.reward > 0: 456 payorgetmoney = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmAcceptGetMoney', numISK=FmtISKWithDescription(c.reward)) 457 args['payorgetmoney'] = payorgetmoney 458 elif c.type == const.conTypeCourier: 459 if c.volume > const_conCourierWarningVolume: 460 if prompt_player_fsd_dialog('ConNeedFreighter', {'vol': c.volume}, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES: 461 return False 462 if c.volume <= const_conCourierWarningVolume: 463 ship = sm.GetService('godma').GetItem(session.shipid) 464 if ship: 465 maxVolume = ship.GetCapacity().capacity 466 if maxVolume < c.volume: 467 if prompt_player_fsd_dialog('ConCourierDoesNotFit', {'shipCapacity': maxVolume, 468 'packageVolume': c.volume}, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES: 469 return False 470 msg = 'ConConfirmAcceptCourier' 471 args['numdays'] = c.numDays 472 args['destination'] = c.endStationID 473 args['volume'] = c.volume 474 collateral = '' 475 if c.collateral > 0: 476 collateral = localization.GetByLabel('UI/Contracts/ContractsService/YouWillHaveToProvideCollateralAmount', numISK=FmtISKWithDescription(c.collateral)) 477 args['collateral'] = collateral 478 else: 479 raise RuntimeError('Invalid contract type') 480 if prompt_player_fsd_dialog(msg, args, uiconst.YESNO) != uiconst.ID_YES: 481 return False 482 self.LogInfo('Accepting contract %s' % contractID) 483 try: 484 contract = self.GetContractProxySvc().AcceptContract(contractID, forCorp) 485 except UserError as e: 486 raise 487 488 if contract.type == const.conTypeCourier: 489 self.contractsInProgress[contract.contractID] = [contract.startStationID, contract.endStationID, contract.dateAccepted + const.DAY * contract.numDays] 490 self.ClearCache() 491 return True 492 493 def DeleteContract(self, contractID): 494 contract = self.GetContract(contractID, force=None) 495 if not contract: 496 raise UserError('ConContractNotFound') 497 c = contract.contract 498 if c.dateExpired >= blue.os.GetWallclockTime() and c.status != const.conStatusRejected: 499 args = {} 500 args['contractname'] = GetContractTitle(c, contract.items) 501 msg = 'ConConfirmDeleteContract' 502 if prompt_player_fsd_dialog(msg, args, uiconst.YESNO) != uiconst.ID_YES: 503 return False 504 self.LogInfo('Deleting contract', contractID) 505 try: 506 if self.GetContractProxySvc().DeleteContract(contractID): 507 sm.ScatterEvent('OnDeleteContracts', [contractID]) 508 except UserError as e: 509 raise 510 511 self.ClearCache() 512 return True 513 514 def DeleteMultipleContracts(self, contractIDs): 515 if not contractIDs: 516 return 517 if prompt_player_fsd_dialog('ConfirmDeleteMultipleContracts', {'numContracts': len(contractIDs)}, uiconst.YESNO) != uiconst.ID_YES: 518 return False 519 try: 520 deleted, failed = self.GetContractProxySvc().DeleteMultipleContracts(contractIDs) 521 if deleted: 522 if failed: 523 text = localization.GetByLabel('UI/Contracts/ContractsService/MultipleContractsDeletedAndFailed', numDeleted=len(deleted), numFailed=len(failed)) 524 else: 525 text = localization.GetByLabel('UI/Contracts/ContractsService/MultipleContractsDeleted', numDeleted=len(deleted)) 526 else: 527 text = localization.GetByLabel('UI/Contracts/ContractsService/MultipleContractsFailedToDelete', numFailed=len(failed)) 528 message_player(text) 529 if deleted: 530 sm.ScatterEvent('OnDeleteContracts', deleted) 531 except UserError as e: 532 raise 533 534 self.ClearCache() 535 return True 536 537 def CompleteContract(self, contractID): 538 ret = False 539 if prompt_player_fsd_dialog('ConConfirmComplete', None, uiconst.YESNO, suppress=uiconst.ID_YES) == uiconst.ID_YES: 540 self.LogInfo('Completing contract %s' % contractID) 541 self.ClearCache() 542 try: 543 ret = self.GetContractProxySvc().CompleteContract(contractID, const.conStatusFinished) 544 except UserError as e: 545 raise 546 547 if contractID in self.contractsInProgress: 548 del self.contractsInProgress[contractID] 549 prompt_player_fsd_dialog('ConCourierContractDelivered') 550 return ret 551 552 def FailContract(self, contractID): 553 contract = self.GetContract(contractID, force=None) 554 c = contract.contract 555 args = {} 556 if not contract: 557 raise UserError('ConContractNotFound') 558 args['contractname'] = GetContractTitle(c, contract.items) 559 if c.acceptorID == session.charid or c.acceptorID == session.corpid: 560 msg = 'ConConfirmFailContractAcceptor' 561 args['collateral'] = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmFailLoseCollateral', numISK=FmtISKWithDescription(c.collateral)) 562 else: 563 dateOverdue = c.dateAccepted + const.DAY * c.numDays 564 isOverdue = blue.os.GetWallclockTime() > dateOverdue 565 if isOverdue and c.status == const.conStatusInProgress: 566 msg = 'ConConfirmFailContractIssuerOverdue' 567 args['timeoverdue'] = FmtDate(blue.os.GetWallclockTime() - dateOverdue, 'ls') 568 else: 569 msg = 'ConConfirmFailContractIssuerFinishedAcceptor' 570 args['acceptor'] = cfg.eveowners.Get(c.acceptorID).name 571 if prompt_player_fsd_dialog(msg, args, uiconst.YESNO) != uiconst.ID_YES: 572 return False 573 self.ClearCache() 574 self.LogInfo('Failing contract %s' % contractID) 575 try: 576 ret = self.GetContractProxySvc().CompleteContract(contractID, const.conStatusFailed) 577 except UserError as e: 578 raise 579 580 if contractID in self.contractsInProgress: 581 del self.contractsInProgress[contractID] 582 return ret 583 584 def RejectContract(self, contractID): 585 contract = self.GetContract(contractID, force=None) 586 c = contract.contract 587 args = {} 588 if not contract: 589 raise UserError('ConContractNotFound') 590 args['contractname'] = GetContractTitle(c, contract.items) 591 args['issuer'] = cfg.eveowners.Get({False: c.issuerID, 592 True: c.issuerCorpID}[c.forCorp]).name 593 if c.assigneeID == session.charid: 594 msg = 'ConConfirmRejectContract' 595 elif c.assigneeID == session.corpid: 596 msg = 'ConConfirmRejectContractCorp' 597 elif session.allianceid and c.assigneeID == session.allianceid: 598 msg = 'ConConfirmRejectContractAlliance' 599 else: 600 raise UserError('ConNotYourContract') 601 if prompt_player_fsd_dialog(msg, args, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES: 602 return False 603 self.LogInfo('Rejecting contract %s' % contractID) 604 self.ClearCache() 605 try: 606 return self.GetContractProxySvc().CompleteContract(contractID, const.conStatusRejected) 607 except UserError as e: 608 raise 609 610 def CreateContract(self, contractInfo, confirm = None): 611 self.LogNotice('CreateContract, contractInfo = ', contractInfo) 612 try: 613 ret = self.GetContractProxySvc().CreateContract(contractInfo, confirm=confirm) 614 except UserError as e: 615 raise 616 617 self.ClearCache() 618 return ret 619 620 def PlaceBid(self, contractID, force = None): 621 uthread.ReentrantLock(self) 622 try: 623 isContractMgr = session.corprole & const.corpRoleContractManager == const.corpRoleContractManager 624 contract = self.GetContract(contractID, force=force) 625 self.contract = contract 626 c = contract.contract 627 if not self.CheckEndpointAccess(c.startStationID): 628 return 629 currentBid = 0 630 numBids = 0 631 maxBid = MAX_AMOUNT 632 if len(contract.bids) > 0: 633 currentBid = contract.bids[0].amount 634 numBids = len(contract.bids) 635 b = currentBid + max(int(0.1 * c.price), 1000) 636 if c.collateral > 0: 637 b = min(b, c.collateral) 638 maxBid = c.collateral 639 minBid = int(max(c.price, b)) 640 if c.collateral > 0: 641 collateral = FmtISKWithDescription(c.collateral) 642 else: 643 collateral = localization.GetByLabel('UI/Contracts/ContractEntry/NoBuyoutPrice') 644 biddingOnLabel = localization.GetByLabel('UI/Contracts/ContractsService/BiddingOnName', contractName=GetContractTitle(c, contract.items)) 645 startingBidLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/StartingBid') 646 buyoutLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/BuyoutPrice') 647 currentLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/CurrentBid') 648 yourBidLabel = localization.GetByLabel('UI/Contracts/ContractsWindow/YourBid') 649 format = [{'type': 'text', 650 'text': biddingOnLabel, 651 'labelwidth': 100}, 652 {'type': 'header', 653 'text': startingBidLabel}, 654 {'type': 'text', 655 'text': FmtISKWithDescription(c.price)}, 656 {'type': 'header', 657 'text': buyoutLabel}, 658 {'type': 'text', 659 'text': collateral}, 660 {'type': 'header', 661 'text': currentLabel}, 662 {'type': 'text', 663 'text': FmtISKWithDescription(currentBid)}, 664 {'type': 'edit', 665 'setvalue': '0.1', 666 'floatonly': [0, maxBid], 667 'setvalue': minBid, 668 'key': 'bid', 669 'labelwidth': 100, 670 'label': yourBidLabel, 671 'required': 1, 672 'setfocus': 1}] 673 if isContractMgr and not (c.forCorp and c.issuerCorpID == session.corpid): 674 format.append({'type': 'checkbox', 675 'required': 1, 676 'height': 16, 677 'setvalue': 0, 678 'key': 'forCorp', 679 'label': '', 680 'text': localization.GetByLabel('UI/Contracts/ContractsWindow/PlaceBidForCorp')}) 681 if c.collateral > 0: 682 format.append({'type': 'checkbox', 683 'required': 1, 684 'height': 16, 685 'setvalue': 0, 686 'key': 'buyout', 687 'label': '', 688 'text': localization.GetByLabel('UI/Contracts/ContractsWindow/Buyout'), 689 'onchange': self.PlaceBidBuyoutCallback}) 690 format.append({'type': 'push'}) 691 retval = uix.HybridWnd(format, localization.GetByLabel('UI/Contracts/ContractsWindow/BiddingOnContract'), 'biddingOnContract', modal=1, buttons=uiconst.OKCANCEL, minW=340, minH=100, icon='res:/ui/Texture/WindowIcons/wallet.png') 692 if retval: 693 forCorp = not not retval.get('forCorp', False) 694 buyout = not not retval.get('buyout', False) 695 bid = int(retval['bid']) 696 if buyout: 697 if c.collateral < c.price: 698 raise RuntimeError('Buyout is lower than starting bid!') 699 bid = c.collateral 700 try: 701 retval = self.DoPlaceBid(contractID, bid, forCorp) 702 except UserError as e: 703 if e.args[0] == 'ConBidTooLow': 704 prompt_player_fsd_dialog(e.args[0], e.args[1]) 705 self.PlaceBid(contractID, force=True) 706 else: 707 raise 708 709 return not not retval 710 finally: 711 uthread.UnLock(self) 712 self.ClearCache() 713 714 def PlaceBidBuyoutCallback(self, chkbox, *args): 715 c = self.contract.contract 716 buyout = c.collateral 717 currentBid = 0 718 if len(self.contract.bids) > 0: 719 currentBid = self.contract.bids[0].amount 720 minBid = int(max(c.price, min(currentBid + 0.1 * c.price + 0.1, c.collateral))) 721 wnd = chkbox.parent.parent 722 edit = wnd.FindChild('edit_bid') 723 if chkbox.GetValue(): 724 edit.state = uiconst.UI_DISABLED 725 edit.SetText(buyout) 726 else: 727 edit.state = uiconst.UI_NORMAL 728 edit.SetText(minBid) 729 730 def DoPlaceBid(self, contractID, bid, forCorp): 731 contract = self.GetContract(contractID, force=None) 732 c = contract.contract 733 args = {} 734 if not contract: 735 raise UserError('ConContractNotFound') 736 wallet = sm.GetService('corp').GetMyCorpAccountName() 737 args = {} 738 if forCorp: 739 args['youoryourcorp'] = localization.GetByLabel('UI/Contracts/ContractsService/YouOnBehalfOfYourCorp', corpName=cfg.eveowners.Get(session.corpid).name, wallet=wallet) 740 else: 741 args['youoryourcorp'] = localization.GetByLabel('UI/Common/You') 742 args['contractname'] = GetContractTitle(c, contract.items) 743 args['amount'] = FmtISKWithDescription(bid) 744 reportGet = '' 745 for item in contract.items: 746 if item.inCrate: 747 reportGet += '<t>%s.<br>' % cfg.FormatConvert(const.UE_TYPEIDANDQUANTITY, item.itemTypeID, max(1, item.quantity)) 748 749 if reportGet != '': 750 reportGet = localization.GetByLabel('UI/Contracts/ContractsService/ConfirmItemsGet', items=reportGet) 751 args['itemsget'] = reportGet 752 msg = 'ConConfirmPlaceBid' 753 if prompt_player_fsd_dialog(msg, args, uiconst.YESNO, suppress=uiconst.ID_YES) != uiconst.ID_YES: 754 return False 755 self.LogInfo('Placing bid of %s on contract %s' % (FmtISKWithDescription(bid), contractID)) 756 try: 757 return self.GetContractProxySvc().PlaceBid(contractID, bid, forCorp) 758 except UserError as e: 759 raise 760 761 def FinishAuction(self, contractID, isIssuer): 762 self.LogInfo('Finishing Auction %s' % contractID) 763 self.ClearCache() 764 try: 765 return self.GetContractProxySvc().FinishAuction(contractID, isIssuer) 766 except UserError as e: 767 raise 768 769 def GetMarketTypes(self): 770 if not self.markettypes: 771 self.markettypes = GetMarketTypes() 772 return self.markettypes 773 774 def NumOutstandingContracts(self, forCorp = False): 775 self.LogInfo('Getting number of outstanding contracts%s' % ['', ' (for corp)'][forCorp]) 776 return self.GetContractProxySvc().NumOutstandingContracts() 777 778 def SplitStack(self, stationID, itemID, qty, forCorp, flag): 779 self.LogInfo('Splitting stack of item %s, qty=%s%s' % (itemID, qty, ['', ' (for corp)'][forCorp])) 780 ret = self.GetContractProxySvc().SplitStack(stationID, itemID, qty, forCorp, flag) 781 if ret: 782 wnd = CreateContractWnd.GetIfOpen() 783 if wnd is not None and not wnd.destroyed: 784 wnd.OnItemSplit(itemID, qty) 785 wnd.Refresh() 786 return ret 787 788 def GetSolarSystemIDForStationOrStructure(self, stationID): 789 if idCheckers.IsStation(stationID): 790 return sm.GetService('ui').GetStationStaticInfo(stationID).solarSystemID 791 else: 792 structureInfo = sm.GetService('structureDirectory').GetStructureInfo(stationID) 793 if structureInfo is not None: 794 return structureInfo.solarSystemID 795 return 796 797 def GetItemsInContainer(self, stationID, containerID, forCorp, flag): 798 return sorted(self.GetContractProxySvc().GetItemsInContainer(stationID, containerID, forCorp, flag) or [], key=lambda i: evetypes.GetName(i.typeID)) 799 800 def GetNumItemsInContainers(self, stationID, containerIDs, forCorp, flag): 801 return self.GetContractProxySvc().GetNumItemsInContainers(stationID, containerIDs, forCorp, flag) 802 803 def GetItemsInDockableLocation(self, stationID, forCorp): 804 return self.GetContractProxySvc().GetItemsInDockableLocation(stationID, forCorp) 805 806 def DeleteNotification(self, contractID, forCorp): 807 self.ClearCache() 808 return self.GetContractProxySvc().DeleteNotification(contractID, forCorp) 809 810 def CollectMyPageInfo(self, force = False): 811 skillsSvc = sm.GetService('skills') 812 if self.myPageInfo is None or self.myPageInfo.timeout < blue.os.GetWallclockTime() or force: 813 mpi = self.GetContractProxySvc().CollectMyPageInfo() 814 contractingLevel = skillsSvc.GetEffectiveLevel(typeContracting) or 0 815 advancedContractingLevel = skillsSvc.GetEffectiveLevel(typeAdvancedContracting) or 0 816 corporationContractingLevel = skillsSvc.GetEffectiveLevel(typeCorporationContracting) or 0 817 maxNumContracts = NUM_CONTRACTS_BY_SKILL[contractingLevel] + NUM_CONTRACTS_BY_ADVSKILL[advancedContractingLevel] 818 maxNumContractsCorp = NUM_CONTRACTS_BY_SKILL_CORP[corporationContractingLevel] 819 mpi.numContractsLeft = max(maxNumContracts - mpi.numOutstandingContractsNonCorp, 0) 820 mpi.numContractsLeftForCorp = max(maxNumContractsCorp - mpi.numOutstandingContractsForCorp, 0) 821 mpi.numContractsTotal = maxNumContracts 822 mpi.numContractsLeftInCorp = max(MAX_NUM_CONTRACTS - mpi.numOutstandingContracts, 0) 823 mpi.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE 824 self.myPageInfo = mpi 825 else: 826 self.LogInfo('CollectMyPageInfo returning cached result') 827 mpi = self.myPageInfo 828 return mpi 829 830 def GetMyExpiredContractList(self): 831 if self.myExpiredContractList is None or self.myExpiredContractList.timeout < blue.os.GetWallclockTime(): 832 l = utillib.KeyVal(mySelf=None, myCorp=None, expires=0) 833 l.mySelf = sm.ProxySvc('contractProxy').GetMyExpiredContractList(False) 834 l.myCorp = sm.ProxySvc('contractProxy').GetMyExpiredContractList(True) 835 l.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE 836 self.myExpiredContractList = l 837 else: 838 self.LogInfo('GetMyExpiredContractList returning cached result') 839 l = self.myExpiredContractList 840 return l 841 842 def GetMyBids(self, forCorp): 843 l = None 844 val = getattr(self, 'myBids_%s' % forCorp, None) 845 if val is None or val.timeout < blue.os.GetWallclockTime(): 846 l = utillib.KeyVal(mySelf=None, myCorp=None, expires=0) 847 l.list = sm.ProxySvc('contractProxy').GetMyBids(forCorp) 848 l.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE 849 setattr(self, 'myBids_%s' % forCorp, l) 850 else: 851 self.LogInfo('GetMyBids(', forCorp, ') returning cached result') 852 l = val 853 return l 854 855 def GetMyContractEscrow(self): 856 iskEscrow = 0.0 857 itemsEscrow = 0.0 858 try: 859 contractEscrow = self.GetContractProxySvc().GetMyContractEscrow() 860 iskEscrow = float(contractEscrow.iskEscrow or 0) 861 itemsEscrow = float(contractEscrow.itemsEscrow or 0) 862 except Exception as exception: 863 msg = 'Failed to calculate total contract escrow, cause: %s' % getattr(exception, 'msg', str(exception)) 864 self.LogException(msg) 865 866 return (iskEscrow, itemsEscrow) 867 868 def GetMyCurrentContractList(self, isAccepted, forCorp): 869 l = None 870 val = getattr(self, 'myCurrentList_%s_%s' % (isAccepted, forCorp), None) 871 if val is None or val.timeout < blue.os.GetWallclockTime(): 872 l = utillib.KeyVal(mySelf=None, myCorp=None, expires=0) 873 l.list = sm.ProxySvc('contractProxy').GetMyCurrentContractList(isAccepted, forCorp) 874 l.timeout = blue.os.GetWallclockTime() + CACHE_TIME * MINUTE 875 setattr(self, 'myCurrentList_%s_%s' % (isAccepted, forCorp), l) 876 else: 877 self.LogInfo('GetMyCurrentContractList(', isAccepted, forCorp, ') returning cached result') 878 l = val 879 return l 880 881 def ClearCache(self): 882 setattr(self, 'myCurrentList_True_True', None) 883 setattr(self, 'myCurrentList_True_False', None) 884 setattr(self, 'myCurrentList_False_True', None) 885 setattr(self, 'myCurrentList_False_False', None) 886 setattr(self, 'myBids_False', None) 887 setattr(self, 'myBids_True', None) 888 self.myExpiredContractList = None 889 self.myPageInfo = None 890 sm.ScatterEvent('OnContractCacheCleared') 891 self._NotifyOfEscrowChange() 892 893 def ShowContracts(self, status = 0, forCorp = 0): 894 wnd = ContractsWindow.GetIfOpen() 895 if not wnd: 896 wnd = ContractsWindow.ToggleOpenClose() 897 wnd.ShowMyContractsPanel(status, forCorp) 898 899 def OpenCreateContract(self, items = None, contract = None, copyContractDisvision = None): 900 CreateContractWnd.Open(recipientID=None, contractItems=items, copyContract=contract, copyContractDisvision=copyContractDisvision) 901 902 def OpenCreateContractFromIGB(self, contractType = None, stationID = None, itemIDs = None): 903 CreateContractWnd.CloseIfOpen() 904 CreateContractWnd.Open(recipientID=None, contractItems=None, copyContract=None, contractType=contractType, stationID=stationID, itemIDs=itemIDs) 905 906 def OpenAvailableTab(self, view, reset = False, typeID = None, contractType = CONTYPE_AUCTIONANDITEMECHANGE): 907 wnd = ContractsWindow.GetIfOpen() 908 if wnd is not None: 909 assigneeID = {3: const.conAvailMyself, 910 4: const.conAvailMyCorp}.get(view, const.conAvailPublic) 911 self.OpenSearchTab(typeID, None, None, None, None, None, assigneeID, contractType) 912 913 def OpenSearchTab(self, *args): 914 wnd = ContractsWindow.Open() 915 blue.pyos.synchro.SleepWallclock(10) 916 wnd.tabGroup.SelectByIdx(2) 917 wnd.contractSearchContent.FindRelated(*args) 918 919 def AddIgnore(self, issuerID): 920 ignoreList = settings.user.ui.Get('contracts_ignorelist', []) 921 if len(ignoreList) >= MAX_IGNORED: 922 raise UserError('ConIgnoreListFull', {'n': MAX_IGNORED}) 923 if issuerID not in ignoreList: 924 ignoreList.append(issuerID) 925 settings.user.ui.Set('contracts_ignorelist', ignoreList) 926 self.ClearCache() 927 sm.ScatterEvent('OnAddIgnore', issuerID) 928 929 def ForgetAuctionContract(self, contractID, forCorp): 930 self.GetContractProxySvc().DeleteContractNotification(contractID, forCorp) 931 self.myExpiredContractList = None 932 933 def OpenIgnoreList(self): 934 IgnoreListWindow.CloseIfOpen() 935 IgnoreListWindow.Open() 936 937 def GetMessages(self): 938 return self.messages 939 940 def DeliverCourierContractFromItemID(self, itemID): 941 info = self.GetBasicContractInfoFromItemID(itemID) 942 if info is None: 943 raise UserError('ConContractNotFoundForCrate') 944 contractID = info.contractID 945 self.CompleteContract(contractID) 946 947 def FindCourierContractFromItemID(self, itemID): 948 info = self.GetBasicContractInfoFromItemID(itemID) 949 if info is None: 950 raise UserError('ConContractNotFoundForCrate') 951 self.ShowContract(info.contractID) 952 953 def GetBasicContractInfoFromItemID(self, itemID): 954 return self.GetContractProxySvc().GetCourierContractFromItemID(itemID) 955 956 def GetEndStationIDForCourierWrapID(self, itemID): 957 contractInfo = self.GetBasicContractInfoFromItemID(itemID) 958 if contractInfo is None: 959 return 960 contract = self.GetContract(contractInfo.contractID, force=True) 961 c = contract.contract 962 return c.endStationID 963 964 @Memoize(1) 965 def IsStationInaccessible(self, stationID): 966 if stationID is None: 967 return False 968 if idCheckers.IsStation(stationID): 969 return False 970 if stationID == session.structureid: 971 return False 972 try: 973 if sm.GetService('structureSettings').CharacterHasService(stationID, structures.SERVICE_DOCKING): 974 return False 975 return True 976 except ValueError: 977 return True 978 979 return False 980 981 def CheckEndpointAccess(self, endpointID): 982 if idCheckers.IsStation(endpointID): 983 return bool(sm.GetService('ui').GetStation(endpointID)) 984 structure = sm.GetService('structureDirectory').GetStructureInfo(endpointID) 985 if structure is not None: 986 if session.structureid == endpointID: 987 return True 988 linkInfo = ('showinfo', structure.typeID) 989 header = localization.GetByLabel('UI/Common/Confirm') 990 question = localization.GetByLabel('UI/Contracts/ContractsService/StructureMayBeInaccessible', structureID=endpointID, typeID=structure.typeID, linkinfo=linkInfo) 991 if prompt_player_fsd_dialog('CustomQuestion', {'header': header, 992 'question': question}, uiconst.YESNO) != uiconst.ID_YES: 993 return False 994 return True 995 raise RuntimeError("Endpoint doesn't resolve to a station or player owned structure") 996 997 def GetSystemSecurityDot(self, solarSystemID): 998 security_status = get_security_status(solarSystemID) 999 color = get_security_status_color(security_status) 1000 return eveformat.color(u'\u2022', color=color) 1001 1002 def OnShowContract(self, contractID): 1003 self.ShowContract(contractID) 1004 1005 def GetContractTypeIDFinder(self): 1006 if self.contractTypeIDFinder is None: 1007 with TempLock('GetContractTypeIDFinder'): 1008 if self.contractTypeIDFinder is None: 1009 typeIDs = [ x.typeID for x in self.GetMarketTypes() if CanRequestType(x.typeID) ] 1010 from textImporting.textToTypeIDFinder import TextToTypeIDFinder 1011 from textImporting import IsUsingDefaultLanguage 1012 usingDefaultLanguage = IsUsingDefaultLanguage(session) 1013 self.contractTypeIDFinder = TextToTypeIDFinder(typeIDs, usingDefaultLanguage) 1014 return self.contractTypeIDFinder