NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

dna.py (36287B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\devtools\script\dna.py
      2 import dogma.data as dogma_data
      3 from carbon.common.script.sys.serviceConst import ROLE_GML, ROLE_SPAWN, ROLE_WORLDMOD
      4 from carbonui.primitives.container import Container
      5 from carbonui.button.group import ButtonGroup
      6 from carbonui.control.buttonIcon import ButtonIcon
      7 from carbonui.control.window import Window
      8 from carbonui.primitives.frame import Frame
      9 from eve.client.script.ui.control.entries.generic import Generic
     10 from eve.client.script.ui.control.entries.util import GetFromClass
     11 from eve.client.script.ui.control.infoIcon import InfoIcon
     12 from player_messaging.client.ui_message import prompt_player
     13 from eve.common.script.sys import eveCfg
     14 from eveexceptions import UserError
     15 from inventorycommon.const import subsystemSlotFlags
     16 from inventorycommon.util import IsShipFittable
     17 import evetypes
     18 import blue
     19 import triui
     20 from eve.client.script.ui.util import uix
     21 from eve.client.script.ui.control import eveIcon, eveLabel, eveScroll
     22 import uthread
     23 import carbonui.const as uiconst
     24 from player_messaging import IGNORE_TO_TOP
     25 USE_SPAWN = True
     26 
     27 def GetTypeName(typeID):
     28     return evetypes.GetName(typeID)
     29 
     30 
     31 Message = lambda title, body, icon = triui.INFO: prompt_player(body, title, options=uiconst.OK, icon=icon)
     32 Progress = lambda title, text, current, total: (title,
     33  text,
     34  current,
     35  total)
     36 Cycle = lambda title, text: sm.GetService('loading').Cycle(title, text)
     37 StopCycle = lambda : sm.GetService('loading').StopCycle()
     38 Slash = lambda command: sm.RemoteSvc('slash').SlashCmd(command)
     39 COPYCAT = 'copycat'
     40 groupByFlag = {}
     41 SLOTGROUPS = (const.flagHiSlot0,
     42  const.flagMedSlot0,
     43  const.flagLoSlot0,
     44  const.flagRigSlot0,
     45  const.flagSubSystemSlot0)
     46 
     47 def InitStuff():
     48     global SLOTGROUPS
     49     if groupByFlag:
     50         return
     51     SLOTGROUPS = (const.flagHiSlot0,
     52      const.flagMedSlot0,
     53      const.flagLoSlot0,
     54      const.flagRigSlot0,
     55      const.flagSubSystemSlot0)
     56     grp = 0
     57     for slot in SLOTGROUPS:
     58         for flag in xrange(slot, slot + 8):
     59             groupByFlag[flag] = grp
     60 
     61         grp += 1
     62 
     63 
     64 def GetSlotGroup(typeID):
     65     try:
     66         for effect in dogma_data.get_type_effects(typeID):
     67             if effect.effectID == const.effectHiPower:
     68                 return 0
     69             if effect.effectID == const.effectMedPower:
     70                 return 1
     71             if effect.effectID == const.effectLoPower:
     72                 return 2
     73             if effect.effectID == const.effectRigSlot:
     74                 return 3
     75             if effect.effectID == const.effectSubSystem:
     76                 return 4
     77 
     78     except KeyError:
     79         return None
     80 
     81 
     82 def Load(typeID, qty = 1, where = 'me'):
     83     try:
     84         return sm.RemoteSvc('slash').SlashCmd('/load %s %s %s' % (where, typeID, qty))[0]
     85     except:
     86         return None
     87 
     88 
     89 def WaitAndSetLabel(itemID, name, timeout = 10):
     90     timeout *= 4
     91     bp = sm.GetService('michelle').GetBallpark()
     92     while timeout:
     93         if bp.GetBall(itemID):
     94             sm.GetService('invCache').GetInventoryMgr().SetLabel(itemID, name)
     95             break
     96         timeout -= 1
     97         blue.pyos.synchro.SleepSim(250)
     98 
     99 
    100 def CreateShip_WORLDMOD(typeID, name = None, subsystems = None):
    101     if eveCfg.IsDocked():
    102         itemID = sm.RemoteSvc('slash').SlashCmd('/createitem %d 1' % (typeID,))
    103         subSystemIDs = []
    104         if subsystems is not None:
    105             for subSystemTypeID in subsystems:
    106                 subSystemID = sm.RemoteSvc('slash').SlashCmd('/createitem %d' % subSystemTypeID)
    107                 subSystemIDs.append(subSystemID)
    108 
    109         sm.GetService('gameui').GetShipAccess().AssembleShip(itemID, name, subSystems=subSystemIDs)
    110     else:
    111         if USE_SPAWN:
    112             return CreateShip_SPAWN(typeID, name)
    113         itemID = sm.RemoteSvc('slash').SlashCmd('/createitem %d' % typeID)
    114         sm.GetService('gameui').GetShipAccess().Jettison([itemID])
    115         if name:
    116             WaitAndSetLabel(itemID, name)
    117     return itemID
    118 
    119 
    120 def CreateShip_SPAWN(typeID, name = None):
    121     if eveCfg.IsDocked():
    122         return CreateShip_GML(typeID, name)
    123     itemID = sm.RemoteSvc('slash').SlashCmd('/spawn %d' % typeID)
    124     if name:
    125         WaitAndSetLabel(itemID, name)
    126     return itemID
    127 
    128 
    129 def CreateShip_GML(typeID, name = None):
    130     shipID = None
    131     itemID = Load(typeID)
    132     invCache = sm.GetService('invCache')
    133     if not itemID:
    134         if eveCfg.IsDocked():
    135             hangar = invCache.GetInventory(const.containerHangar)
    136             cargo = invCache.GetInventoryFromId(eveCfg.GetActiveShip()).GetCapacity(const.flagCargo)
    137             if cargo.capacity - cargo.used >= 100:
    138                 shipID = Load(11019)
    139                 if shipID:
    140                     hangar.Add(shipID, eveCfg.GetActiveShip(), qty=1)
    141                     sm.GetService('gameui').GetShipAccess().AssembleShip(shipID, 'Copycat Temporary')
    142                     itemID = Load(typeID, where=shipID)
    143                     if not itemID:
    144                         Message('what the?!', 'Something is hosed alright.')
    145                         return
    146     if itemID:
    147         if eveCfg.IsDocked():
    148             invCache.GetInventory(const.containerHangar).Add(itemID, eveCfg.GetActiveShip(), qty=1)
    149             sm.GetService('gameui').GetShipAccess().AssembleShip(itemID, name)
    150         else:
    151             sm.GetService('gameui').GetShipAccess().Jettison([itemID])
    152             if name:
    153                 WaitAndSetLabel(itemID, name)
    154     if shipID and eveCfg.IsDocked():
    155         invCache.GetInventoryMgr().TrashItems([shipID], session.stationid or session.structureid)
    156     if not itemID:
    157         Message('Planck field overload', "Sorry, you can't squeeze a <color=0xffffff00>%s<color=0xffffffff> into your current ship's cargohold.<br><br>Please make sure you have enough free cargo capacity to load this ship before trying again.<br>" % evetypes.GetName(typeID))
    158     return itemID
    159 
    160 
    161 def CreateShip(typeID, name = None, subsystems = None):
    162     if session.role & ROLE_WORLDMOD:
    163         return CreateShip_WORLDMOD(typeID, name, subsystems)
    164     elif session.role & ROLE_SPAWN and USE_SPAWN:
    165         return CreateShip_SPAWN(typeID, name)
    166     elif session.role & ROLE_GML:
    167         return CreateShip_GML(typeID, name)
    168     else:
    169         return None
    170 
    171 
    172 class Ship():
    173 
    174     def __init__(self, shipID = None, dnaKey = None, name = None):
    175         self.source = None
    176         self.dna = []
    177         self.typeID = 0
    178         self.moduleByFlag = {}
    179         self.name = None
    180         self.valid = False
    181         self.errorMessage = 'Template not initialized'
    182         self.errorTypeID = 0
    183         InitStuff()
    184         if shipID:
    185             self.ImportFromShip(shipID=shipID)
    186             return
    187         if dnaKey:
    188             self.ImportFromDNA(dnaKey, name)
    189             return
    190 
    191     def itermodules(self, smart = False, banks = None):
    192         if banks is None:
    193             banks = SLOTGROUPS
    194         get = self.moduleByFlag.get
    195         if smart:
    196             for slot in banks:
    197                 fit = []
    198                 go = False
    199                 for flag in xrange(slot + 7, slot - 1, -1):
    200                     typeID = get(flag, 0)
    201                     if typeID:
    202                         go = True
    203                     if go:
    204                         fit.insert(0, (flag, typeID))
    205 
    206                 for flag, typeID in fit:
    207                     yield (flag, typeID)
    208 
    209         else:
    210             for slot in banks:
    211                 for flag in xrange(slot, slot + 8):
    212                     yield (flag, get(flag, 0))
    213 
    214     def ImportFromShip(self, shipID = None, ownerID = None, deferred = False):
    215         if deferred:
    216             self.deferred = (shipID, ownerID)
    217             return self
    218         self.__init__()
    219         if shipID is None:
    220             shipID = eveCfg.GetActiveShip()
    221         if ownerID is None:
    222             ownerID = session.charid
    223         loc = cfg.evelocations.GetIfExists(shipID)
    224         if loc:
    225             self.name = loc.name
    226         else:
    227             self.name = None
    228         if ownerID != session.charid:
    229             dna = sm.RemoteSvc('slash').SlashCmd('/getshipsetup %d' % shipID)
    230             return self.ImportFromDNA(dna)
    231         if shipID == eveCfg.GetActiveShip():
    232             ship = sm.GetService('clientDogmaIM').GetDogmaLocation().GetDogmaItem(shipID)
    233             if ship is None:
    234                 self.errorMessage = 'Could not get shipID: %s' % shipID
    235                 return self
    236             self.typeID = ship.typeID
    237             for module in ship.GetFittedItems().itervalues():
    238                 if IsShipFittable(module.categoryID):
    239                     self.moduleByFlag[module.flagID] = module.typeID
    240 
    241         else:
    242             try:
    243                 shipinv = sm.GetService('invCache').GetInventoryFromId(shipID)
    244                 self.typeID = shipinv.typeID
    245                 if not self.name:
    246                     self.name = evetypes.GetName(self.typeID)
    247                 mods = shipinv.ListHardwareModules()
    248             except:
    249                 self.errorMessage = 'Could not get inv of shipID: %s' % shipID
    250                 return self
    251 
    252             for rec in mods:
    253                 if IsShipFittable(evetypes.GetCategoryID(rec.typeID)):
    254                     self.moduleByFlag[rec.flagID] = rec.typeID
    255 
    256         self.valid = True
    257         self.Update()
    258         return self
    259 
    260     def ImportFromDNA(self, dna, name = None):
    261         self.__init__()
    262         if dna[:4] != 'DNA:':
    263             self.errorMessage = 'Not a DNA key: %s' % (dna,)
    264             return self
    265         if name:
    266             self.name = name
    267         self.source = dna
    268         self.dna = dna.split(':')[1:]
    269         expectedCategoryIDs = [const.categoryShip]
    270         flags = list(SLOTGROUPS)
    271         try:
    272             for frag in self.dna:
    273                 try:
    274                     parts = map(int, frag.split('*', 1))
    275                 except:
    276                     raise RuntimeError(-1, "Invalid DNA fragment: '%s'" % frag)
    277 
    278                 if len(parts) == 2:
    279                     typeID, multi = parts
    280                 else:
    281                     typeID, = parts
    282                     multi = 1
    283                 self.errorTypeID = typeID
    284                 if multi < 1 or multi > 8:
    285                     raise RuntimeError(typeID, 'Invalid module count: %s' % multi)
    286                 if typeID > 3:
    287                     if not evetypes.Exists(typeID):
    288                         raise RuntimeError(typeID, 'Type not found')
    289                     categoryID = evetypes.GetCategoryID(typeID)
    290                 while multi > 0:
    291                     if typeID <= 3:
    292                         grp = typeID
    293                         self.moduleByFlag[flags[grp]] = 0
    294                         flags[grp] += 1
    295                         multi -= 1
    296                         continue
    297                     if categoryID not in expectedCategoryIDs:
    298                         raise RuntimeError(typeID, 'Expected %s type, got %s type' % (evetypes.GetCategoryNameByCategory(expectedCategoryIDs[0]), evetypes.GetCategoryNameByCategory(categoryID)))
    299                     if categoryID == const.categoryShip:
    300                         self.typeID = typeID
    301                         expectedCategoryIDs = [const.categoryModule, const.categorySubSystem]
    302                     elif IsShipFittable(categoryID):
    303                         grp = GetSlotGroup(typeID)
    304                         if grp is not None:
    305                             self.moduleByFlag[flags[grp]] = typeID
    306                             flags[grp] += 1
    307                         else:
    308                             raise RuntimeError(typeID, 'Type is not a module or rig')
    309                     else:
    310                         raise RuntimeError(typeID, 'Type is not a ship or module')
    311                     multi -= 1
    312 
    313             for startFlag, endFlag in zip(SLOTGROUPS, flags):
    314                 if endFlag > startFlag + 8:
    315                     raise RuntimeError(-1, 'Malformed template')
    316 
    317             self.valid = True
    318             self.Update()
    319         except RuntimeError as e:
    320             self.errorTypeID, self.errorMessage = e
    321 
    322         return self
    323 
    324     def Update(self):
    325         real = 0
    326         fake = 0
    327         for flag, typeID in self.itermodules(smart=True):
    328             if typeID:
    329                 real += 1
    330             else:
    331                 fake += 1
    332 
    333         self.realmodulecount = real
    334         self.fakemodulecount = fake
    335 
    336     def AssembleMany(self, amount = None):
    337         self.NoPodCheck(wantNewShip=True)
    338         if amount is None:
    339             result = uix.QtyPopup(maxvalue=50, minvalue=1, caption='Mass Manufacture', label='', hint='Specify amount of ships to assemble (Max. 50).<br>Note: this function cannot be aborted once running.')
    340             if result:
    341                 amount = result['qty']
    342             else:
    343                 amount = 0
    344         for x in xrange(amount):
    345             self.Assemble()
    346 
    347     def Assemble(self, clone = 0):
    348         if not self.Valid(report=True):
    349             return None
    350         self.NoPodCheck(wantNewShip=True)
    351         tname = GetTypeName(self.typeID)
    352         title = ['Create %s', 'Clone %s'][clone] % tname
    353         sm.GetService('loading').ProgressWnd(title, 'Spawning: %s ...' % tname, 0, 1)
    354         subsystems = []
    355         for flagID in subsystemSlotFlags:
    356             if flagID in self.moduleByFlag:
    357                 subsystems.append(self.moduleByFlag[flagID])
    358 
    359         if session.solarsystemid and len(subsystems) > 0:
    360             raise UserError('CustomInfo', {'info': "You can't assemble ship with subsystems in space. Please try again in station"})
    361         shipID = CreateShip(self.typeID, self.name, subsystems)
    362         if shipID:
    363             self.Fit(shipID, fromAssemble=True)
    364             sm.GetService('loading').ProgressWnd(title, 'Success', 1, 1)
    365             return shipID
    366         sm.GetService('loading').ProgressWnd(title, 'Failed', 1, 1)
    367 
    368     def Fit(self, shipID = None, fromAssemble = False):
    369         if not self.Valid(report=True):
    370             return None
    371         self.NoPodCheck(wantNewShip=fromAssemble)
    372         if not shipID:
    373             shipID = eveCfg.GetActiveShip()
    374         if not fromAssemble:
    375             try:
    376                 shipTypeID = sm.GetService('clientDogmaIM').GetDogmaLocation().GetDogmaItem(shipID).GetTypeID()
    377             except Exception:
    378                 shipTypeID = sm.GetService('invCache').GetInventoryFromId(shipID).typeID
    379 
    380             if shipTypeID != self.typeID:
    381                 Message('Unable to refit', 'Sorry, this setup is designed for a %s. You are currently piloting a %s.' % (GetTypeName(self.typeID), GetTypeName(shipTypeID)))
    382                 raise UserError(IGNORE_TO_TOP)
    383         if session.role & ROLE_GML:
    384             return self.FitUsingSlash(shipID)
    385         if eveCfg.IsDocked():
    386             self.SkillCheck()
    387             return self.FitUsingHangar(shipID)
    388         self.Oops('Oops', 'Fitting operations cannot be done in space. Please dock at a station and try again.')
    389 
    390     def FitUsingHangar(self, shipID):
    391         if shipID != eveCfg.GetActiveShip():
    392             self.Oops('Oops', 'You cannot fit ships other than your active one.')
    393         dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    394         ship = dogmaLocation.GetDogmaItem(shipID)
    395         if ship is None:
    396             self.Oops('Huh?', 'Where is your ship?')
    397         title = 'Fitting %s' % GetTypeName(self.typeID)
    398         setup = self.GetModuleInfo()
    399         Cycle(title, 'Locating required modules...')
    400         source = {}
    401         for typeID in setup['modulelist']:
    402             source[typeID] = []
    403 
    404         fit = {}
    405         for flag, typeID in self.moduleByFlag.iteritems():
    406             if typeID:
    407                 fit[flag] = typeID
    408 
    409         for item in ship.GetFittedItems().itervalues():
    410             if item.typeID == self.moduleByFlag[item.flagID]:
    411                 if item.flagID in fit:
    412                     del fit[item.flagID]
    413             else:
    414                 source[typeID].append(item)
    415             if item.typeID in setup:
    416                 setup[item.typeID] -= 1
    417 
    418         invCache = sm.GetService('invCache')
    419         for item in invCache.GetInventory(const.containerHangar).List(const.flagHangar):
    420             if IsShipFittable(item.categoryID):
    421                 if item.typeID in setup:
    422                     if item.singleton:
    423                         source[item.typeID].insert(0, item)
    424                     else:
    425                         source[item.typeID].append(item)
    426                     setup[item.typeID] -= item.stacksize
    427 
    428         missing = []
    429         for typeID in setup['modulelist']:
    430             if setup[typeID] > 0:
    431                 missing.append('  %s %s' % (setup[typeID], GetTypeName(typeID)))
    432 
    433         if missing:
    434             StopCycle()
    435             msg = 'Sorry, you cannot fit your ship with this setup; The following modules are not in your possession:<br><br>'
    436             msg += '<br>'.join(missing)
    437             self.Oops('Unable to fit ship', msg)
    438         Cycle(title, 'Unfitting modules...')
    439         charges = {}
    440         for item in ship.subLocations.itervalues():
    441             charges[item.flagID] = item
    442 
    443         unfit = []
    444         for item in ship.GetFittedItems().itervalues():
    445             if item.categoryID == const.categoryCharge:
    446                 charges[item.flagID] = item
    447             elif self.moduleByFlag[item.flagID] != item.typeID:
    448                 unfit.append(item)
    449 
    450         ids = []
    451         for item in unfit:
    452             charge = charges.get(item.flagID, None)
    453             if charge:
    454                 ids.append(charge.itemID)
    455             ids.append(item.itemID)
    456 
    457         invCache.GetInventory(const.containerHangar).MultiAdd(ids, shipID, flag=const.flagHangar)
    458         del charges
    459         del unfit
    460         del ids
    461         del item
    462         fitByTypeID = {}
    463         for slot in (const.flagLoSlot0, const.flagMedSlot0, const.flagHiSlot0):
    464             for flag in xrange(slot, slot + 8):
    465                 if flag in fit:
    466                     typeID = fit[flag]
    467                     if typeID in fitByTypeID:
    468                         fitByTypeID[typeID].append(flag)
    469                     else:
    470                         fitByTypeID[typeID] = [flag]
    471 
    472         total = len(fit)
    473         state = [1, False]
    474 
    475         def _fit(typeID, flags):
    476             for flag in flags:
    477                 sm.GetService('loading').ProgressWnd(title, 'Fitting modules...', state[0], total)
    478                 try:
    479                     while source[typeID]:
    480                         item = source[typeID][0]
    481                         try:
    482                             if item.stacksize == 1:
    483                                 source[typeID].pop(0)
    484                             invCache.GetInventoryFromId(shipID).Add(item.itemID, item.locationID, qty=1, flag=flag)
    485                             state[0] += 1
    486                             break
    487                         except:
    488                             pass
    489 
    490                 except:
    491                     state[1] = True
    492                     break
    493 
    494         parallelCalls = []
    495         for typeID, flags in fitByTypeID.iteritems():
    496             parallelCalls.append((_fit, (typeID, flags)))
    497 
    498         StopCycle()
    499         try:
    500             uthread.parallel(parallelCalls)
    501         except:
    502             pass
    503 
    504         if state[1]:
    505             StopCycle()
    506             self.Oops('There was a problem...', 'Your hangar inventory changed while fitting the ship, one or more required modules are locked or no longer within your reach.<br>The fitting operation has been aborted.')
    507         sm.GetService('loading').ProgressWnd(title, 'Done', 1, 1)
    508 
    509     def FitUsingSlash(self, shipID):
    510         title = 'Fitting %s' % GetTypeName(self.typeID)
    511         state = [0, 0]
    512 
    513         def _fitgroup(slot):
    514             w = sm.RemoteSvc('slash')
    515             for flag, typeID in self.itermodules(banks=(slot,), smart=True):
    516                 if typeID:
    517                     state[0] += 1
    518                     modname = GetTypeName(typeID)
    519                 else:
    520                     continue
    521                 state[1] += 1
    522                 sm.GetService('loading').ProgressWnd(title, 'Fitting module %s/%s: %s' % (state[0], self.realmodulecount, modname), state[1] * 1000, (self.realmodulecount + self.fakemodulecount) * 1000 + 1)
    523                 w.SlashCmd('/fit %s %s flag=%d' % (shipID, typeID, flag))
    524 
    525         if shipID == eveCfg.GetActiveShip():
    526             sm.RemoteSvc('slash').SlashCmd('/unload me all')
    527         parallelCalls = []
    528         for slot in SLOTGROUPS:
    529             parallelCalls.append((_fitgroup, (slot,)))
    530 
    531         uthread.parallel(parallelCalls)
    532         sm.GetService('loading').ProgressWnd(title, 'Done', 1, 1)
    533 
    534     def NoPodCheck(self, wantNewShip = False):
    535         dogmaLocation = sm.GetService('clientDogmaIM').GetDogmaLocation()
    536         inPod = not (eveCfg.GetActiveShip() is not None and dogmaLocation.GetDogmaItem(eveCfg.GetActiveShip()).groupID != 29)
    537         if not inPod:
    538             return
    539         if inPod and wantNewShip and session.role & (ROLE_WORLDMOD | ROLE_SPAWN):
    540             return
    541         self.Oops('Allergic to eggs', 'This feature cannot be used when you are in a pod.<br>Get yourself in a ship and try again!')
    542 
    543     def SkillCheck(self, setup = None):
    544         if not setup:
    545             setup = self.GetModuleInfo()
    546         Cycle('   Preparing...', 'Checking skill requirements')
    547         skills = {}
    548         groups = sm.GetService('skills').GetSkillGroups()
    549         for _each in groups:
    550             for skill in _each[1]:
    551                 skills[skill.typeID] = skill.skillLevel
    552 
    553         required = {}
    554         for eachTypeID in setup['modulelist']:
    555             c = 0
    556             for typeID, requiredLevel in sm.RemoteSvc('dogmaIM').GetRequiredSkillLevels(eachTypeID).iteritems():
    557                 c += 1
    558                 if not (typeID in skills and skills[typeID] >= requiredLevel):
    559                     required[typeID] = max(required.get(typeID, 0), requiredLevel)
    560 
    561         StopCycle()
    562         if required:
    563             rs = []
    564             for typeID, level in required.iteritems():
    565                 rs.append('  %s level %s' % (GetTypeName(typeID), level))
    566 
    567             msg = 'To fit this setup to your ship you require the following skills:<br><br>'
    568             msg += '<br>'.join(rs)
    569             self.Oops('Insufficient Skill', msg)
    570 
    571     def GetModuleInfo(self):
    572         setup = {}
    573         setup['modulelist'] = []
    574         for flag, typeID in self.itermodules():
    575             if typeID:
    576                 if typeID in setup:
    577                     setup[typeID] += 1
    578                 else:
    579                     setup['modulelist'].append(typeID)
    580                     setup[typeID] = 1
    581 
    582         return setup
    583 
    584     def ExportAsDNA(self):
    585         if not self.Valid():
    586             return self.ErrorReport()
    587         dna = 'DNA:%s' % self.typeID
    588         multi = 1
    589         lastID = None
    590         grp = 0
    591         get = self.moduleByFlag.get
    592         for flag, typeID in self.itermodules(smart=True):
    593             if not typeID:
    594                 typeID = groupByFlag[flag]
    595             if typeID == lastID:
    596                 multi += 1
    597             else:
    598                 if multi > 1:
    599                     dna += '*%s' % multi
    600                     multi = 1
    601                 dna += ':%s' % typeID
    602             lastID = typeID
    603 
    604         if multi > 1:
    605             dna += '*%s' % multi
    606         return dna
    607 
    608     def ExportAsTriText(self, modulesOnly = False, maxLen = 0):
    609         if not self.Valid():
    610             return self.ErrorReport()
    611         text = ''
    612         if modulesOnly:
    613             indent = ''
    614         else:
    615             indent = '  '
    616             if self.name:
    617                 text += '<color=0xffffff00>Name:<color=0xffffffff> %s<br>' % self.name
    618             text += '<color=0xffffff00>Ship:<color=0xffffffff> %s<br>' % GetTypeName(self.typeID)
    619             text += '<color=0xffffff00>Modules: <color=0xffc0c0c0><br>'
    620         info = self.GetModuleInfo()
    621         if not info['modulelist']:
    622             text += indent + 'None<br>'
    623         if maxLen:
    624             for typeID in info['modulelist']:
    625                 name = GetTypeName(typeID)
    626                 if len(name) > maxLen:
    627                     name = name[:maxLen] + '...'
    628                 text += indent + '%s %s<br>' % (info[typeID], name)
    629 
    630         else:
    631             for typeID in info['modulelist']:
    632                 text += indent + '%s %s<br>' % (info[typeID], GetTypeName(typeID))
    633 
    634         if not modulesOnly:
    635             text += '<br><color=0xffffff00>Key:<color=0xffc0c0c0><br>%s' % self.ExportAsDNA()
    636         return text
    637 
    638     def ExportAsText(self, modulesOnly = False, maxLen = 0):
    639         r = self.ExportAsTriText(modulesOnly, maxLen).replace('<br>', '\r\n') + '\r\n'
    640         while 1:
    641             i = r.find('<color=0x')
    642             if i == -1:
    643                 break
    644             r = r[:i] + r[i + 18:]
    645 
    646         return r
    647 
    648     def ExportAsHTML(self):
    649         if not self.Valid():
    650             msg = '<html><body><h1><font color="red">?TEMPLATE ERROR</font></h1><br>'
    651             msg += self.ErrorReport()
    652             msg += '</body></html>'
    653             return msg
    654         html = '<html><title>Copycat</title><body link="#4080FF">'
    655         html += '<a href="showinfo:%s"><img style="margin: 0px 0px 4px 0px;" src="typeicon:%s" align="right" width="64" height="64"></a>' % (self.typeID, self.typeID)
    656         html += '<font color="yellow">Ship:</font>'
    657         html += '<font size="+2"> %s</font>' % GetTypeName(self.typeID)
    658         if self.name:
    659             html += '<br><font color="yellow">Name:</font> %s' % self.name
    660         else:
    661             html += ''
    662         info = self.GetModuleInfo()
    663         html += '<br><br><font color="yellow">Modules:</font><br>'
    664         if info['modulelist']:
    665             for typeID in info['modulelist']:
    666                 html += '&nbsp;&nbsp;&nbsp;%s <a href="showinfo:%s">%s</a><br>' % (info[typeID], typeID, GetTypeName(typeID))
    667 
    668             html += '<br><font color="yellow">Fitting overview:</font><br>'
    669             for grp in xrange(3):
    670                 if not self.modules[grp]:
    671                     continue
    672                 if grp == 1:
    673                     html += '<img src="icon:09_14" width="16" height="32">'
    674                 for typeID in self.modules[grp]:
    675                     if typeID != 0:
    676                         html += '<a href="showinfo:%s"><img alt="%s" src="typeicon:%s" width="32" height="32"></a>' % (typeID, GetTypeName(typeID), typeID)
    677                     else:
    678                         html += '<img src="icon:09_14" width="32" height="32">'
    679 
    680                 html += '<br>'
    681 
    682         else:
    683             html += 'No modules fitted<br><br>'
    684         html += '</body></html>'
    685         return html
    686 
    687     def ExportAsIGBLink(self, label = None):
    688         if label is None:
    689             label = self.name
    690         return '<a href="localsvc:method=ShowCopyCatIGB&action=open&key=%s&name=%s">%s</a>' % (self.ExportAsDNA(), self.name, label)
    691 
    692     def ExportToClipboard(self, mode = 1):
    693         if mode == 0:
    694             blue.pyos.SetClipboardData(self.ExportAsText())
    695         elif mode == 1:
    696             blue.pyos.SetClipboardData(self.ExportAsDNA())
    697         elif mode == 2:
    698             blue.pyos.SetClipboardData(self.ExportAsIGBLink())
    699 
    700     def Valid(self, report = False):
    701         if self.valid:
    702             return True
    703         if hasattr(self, 'deferred'):
    704             self.ImportFromShip(shipID=self.deferred[0], ownerID=self.deferred[1])
    705         if self.valid:
    706             return True
    707         if report:
    708             Message('Invalid Template', self.ErrorReport(), icon=triui.ERROR)
    709         return False
    710 
    711     def Oops(self, title, message):
    712         Message(title, message)
    713         raise UserError(IGNORE_TO_TOP)
    714 
    715     def ErrorReport(self):
    716         msg = 'The template does not contain valid data.<br><br>Maybe it is because of this error:<br>  %s: %s' % (self.errorTypeID, self.errorMessage)
    717         if self.source:
    718             msg += '<br><br>Key: %s' % self.source
    719         return msg
    720 
    721     def ShowInfo(self, buttons = False):
    722         Popup(self, buttons=buttons)
    723 
    724     def ShowCargo(self, id = None, *args):
    725         cargoContents = []
    726         try:
    727             if id:
    728                 itemID = id
    729             else:
    730                 itemID = self.deferred[0]
    731         except:
    732             return
    733 
    734         for cargo in sm.GetService('invCache').GetInventoryFromId(itemID).ListCargo():
    735             if cargo.categoryID != const.categoryOwner:
    736                 cargoContents.append(('%s<t>%s' % (evetypes.GetName(cargo.typeID), cargo.stacksize), cargo))
    737 
    738         uix.ListWnd(cargo, listtype='generic', caption='Cargo loaded with %d items' % len(cargoContents), isModal=0, minChoices=0, scrollHeaders=[u'Type', u'Quantity'])
    739 
    740     def Store(self):
    741         sm.GetService(COPYCAT).DoStore(self.ExportAsDNA(), self.name)
    742 
    743     def RefitMenu(self):
    744         m = []
    745 
    746         def _Refit(dnaKey):
    747             self.__class__().ImportFromDNA(dnaKey).Fit(eveCfg.GetActiveShip())
    748 
    749         for name, dnaKey in sm.GetService('copycat').GetSetupsByTypeID(self.typeID):
    750             m.append((name, _Refit, (dnaKey,)))
    751 
    752         if m:
    753             m.insert(0, ('My %s setups' % evetypes.GetName(self.typeID), None))
    754         return m
    755 
    756     def GetMenuInline(self, disabled = False, store = True, assemble = True, info = True, fit = True, refit = False, spiffy = False):
    757 
    758         def d(func, condition = None):
    759             if disabled:
    760                 return
    761             if condition is None or condition:
    762                 return func
    763 
    764         m = []
    765         if info:
    766             m += [(('Show Info', 'res:/UI/Texture/Icons/38_16_208.png'), self.ShowInfo), None]
    767         if store:
    768             m += [(('Store Setup', 'res:/UI/Texture/Icons/41_64_1.png'), self.Store)]
    769         if refit:
    770             m += [(('Switch Setup', 'res:/ui/Texture/WindowIcons/fitting.png'), self.RefitMenu())]
    771         if assemble:
    772             if session.role & (ROLE_GML | ROLE_WORLDMOD):
    773                 m += [(('Assemble Ship', 'res:/UI/Texture/Icons/41_64_2.png'), d(self.Assemble, session.role & (ROLE_GML | ROLE_WORLDMOD))), (('Mass Manufacture', 'res:/UI/Texture/Icons/27_64_1.png'), d(self.AssembleMany, session.role & (ROLE_GML | ROLE_WORLDMOD)))]
    774             if fit:
    775                 m += [(('Fit to active Ship', 'res:/UI/Texture/Icons/8_64_11.png'), d(self.Fit))]
    776         if store or assemble or fit:
    777             m.append(None)
    778         if not disabled:
    779             m += [(('Copy Manifest', 'res:/UI/Texture/Icons/10_64_16.png'), d(self.ExportToClipboard), (0,)), (('Copy DNA Key', 'res:/ui/Texture/WindowIcons/attributes.png'), d(self.ExportToClipboard), (1,)), (('Copy IGB Link', 'res:/ui/Texture/WindowIcons/browser.png'), d(self.ExportToClipboard), (2,))]
    780         if spiffy is True:
    781             return m
    782         else:
    783             m2 = []
    784             for x in m:
    785                 if x:
    786                     m2.append((x[0][0],) + x[1:])
    787                 else:
    788                     m2.append(None)
    789 
    790             return m2
    791 
    792 
    793 TOOLSIZE = 34
    794 ICONSIZE = 72
    795 
    796 class InfoPanel(Container):
    797     __guid__ = 'xtriui.DNAInfoPanel'
    798 
    799     def Setup(self, template = None, readonly = False):
    800         self.typeID = None
    801         self.readonly = readonly
    802         self.boven = Container(name='ccip_top', parent=self, height=ICONSIZE, align=uiconst.TOTOP)
    803         self.toolbar = Container(name='ccip_bar', parent=self.boven, left=ICONSIZE + 2, top=ICONSIZE - TOOLSIZE, height=TOOLSIZE, align=uiconst.TOPLEFT, width=240)
    804         Container(name='ccip_push', parent=self, height=3, align=uiconst.TOTOP)
    805         cont = Container(name='mainicon', parent=self.boven, width=ICONSIZE, align=uiconst.TOLEFT)
    806         self.infoframe = Frame(parent=cont, color=(1.0, 1.0, 1.0, 0.5))
    807         self.infoicon = infoicon = InfoIcon(left=1, top=1, parent=cont, idx=0, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED)
    808         infoicon.OnClick = lambda *x: self.ShowInfo()
    809         self.icon = eveIcon.Icon(parent=cont)
    810         self.icon.OnClick = self.ShowInfo
    811         self.icon.LoadTexture('res:/UI/Texture/notavailable.dds')
    812         self.icon.width = self.icon.height = ICONSIZE
    813         Container(name='ccip_push', parent=self.boven, width=6, align=uiconst.TOLEFT)
    814         cont = Container(name='ccip_icon', parent=self.boven, align=uiconst.TOTOP, height=24)
    815         self.capt = eveLabel.CaptionLabel(text='', parent=cont)
    816         cont = Container(name='ccip_textcont', parent=self.boven, align=uiconst.TOALL, pos=(0, 0, 0, 0))
    817         self.text = eveLabel.Label(text='', parent=cont, align=uiconst.TOTOP, height=self.boven.height, state=uiconst.UI_NORMAL)
    818         self.scroll = eveScroll.Scroll(name='ccip_top', parent=self)
    819         if template:
    820             self.Load(template)
    821         else:
    822             self.typeID = None
    823             self.capt.text = 'Copycat'
    824             self.text.text = 'Select a ship setup from the list to display details in this panel'
    825             self.scroll.Load(contentList=[], headers=['qty', 'name'])
    826 
    827     def ShowInfo(self, *args):
    828         if self.typeID:
    829             sm.GetService('info').ShowInfo(typeID=self.typeID)
    830 
    831     def Clicketyclick(self, item):
    832         f = item[1]
    833         if f:
    834             if len(item) >= 3:
    835                 f(*item[2])
    836             else:
    837                 f()
    838 
    839     def Load(self, template):
    840         entries = []
    841         headers = ['qty', 'name']
    842         state = uiconst.UI_NORMAL
    843         if template:
    844             if template.Valid():
    845                 self.typeID = template.typeID
    846                 info = template.GetModuleInfo()
    847                 capt = GetTypeName(template.typeID)
    848                 name = template.name
    849                 for typeID in info['modulelist']:
    850                     entries.append(GetFromClass(Generic, {'label': '%s<t>%s' % (info[typeID], GetTypeName(typeID)),
    851                      'typeID': typeID,
    852                      'showinfo': True}))
    853 
    854             else:
    855                 state = uiconst.UI_HIDDEN
    856                 capt = 'TEMPLATE ERROR'
    857                 name = 'This template does not contain a valid ship setup'
    858                 entries.append(GetFromClass(Generic, {'label': '%s<t>%s' % (template.errorTypeID, template.errorMessage)}))
    859                 headers = ['typeID', 'error']
    860         else:
    861             state = uiconst.UI_HIDDEN
    862             capt = name = ''
    863         self.toolbar.Flush()
    864         if state == uiconst.UI_NORMAL:
    865             self.icon.LoadIconByTypeID(template.typeID)
    866             m = template.GetMenuInline(info=False, store=not self.readonly, spiffy=True)
    867             x = 0
    868             for item in m:
    869                 if item:
    870                     hint, icon = item[0]
    871                     ButtonIcon(parent=self.toolbar, align=uiconst.TOPLEFT, pos=(x,
    872                      0,
    873                      TOOLSIZE,
    874                      TOOLSIZE), texturePath=icon, iconSize=TOOLSIZE, hint=hint, func=lambda _item = item: self.Clicketyclick(_item))
    875                     x += TOOLSIZE
    876 
    877         else:
    878             self.typeID = None
    879             self.icon.LoadTexture('res:/UI/Texture/notavailable.dds')
    880         self.capt.text = capt
    881         self.text.text = name
    882         self.infoframe.state = self.infoicon.state = self.toolbar.state = state
    883         self.scroll.Load(contentList=entries, headers=headers)
    884 
    885 
    886 class Popup():
    887     __wndname__ = 'DNA Popup'
    888 
    889     def __init__(self, key_or_template, name = None, buttons = True, store = True):
    890         if isinstance(key_or_template, Ship):
    891             self.t = key_or_template
    892         else:
    893             self.t = Ship().ImportFromDNA(key_or_template, name)
    894         _wndname = '%s_%s' % (Popup.__wndname__, blue.os.GetWallclockTime())
    895         wnd = Window.Open(windowID=_wndname)
    896         wnd.SetCaption('Ship Setup Information')
    897         wnd.sr.main = wnd.GetChild('main')
    898         wnd.SetMinSize((340, 256))
    899         main = Container(name='main', parent=wnd.sr.main, pos=(const.defaultPadding,
    900          const.defaultPadding,
    901          const.defaultPadding,
    902          const.defaultPadding), align=uiconst.TOALL)
    903         info = InfoPanel(name='info', parent=main, pos=(0, 0, 0, 0))
    904         info.Setup(self.t)
    905         if buttons:
    906             buttons = [['Close',
    907               self.Close,
    908               None,
    909               81]]
    910             wnd.sr.main.children.insert(0, ButtonGroup(btns=buttons))
    911 
    912     def Spawn(self, *args):
    913         self.wnd.Close()
    914         self.t.Assemble()
    915 
    916     def Store(self, *args):
    917         if sm.GetService(COPYCAT).DoStore(self.t.ExportAsDNA(), self.t.name):
    918             self.wnd.Close()
    919 
    920     def Close(self, *args):
    921         self.wnd.Close()
    922         del self.wnd