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

character.py (72619B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\services\character.py
      2 from __future__ import print_function
      3 import colorsys
      4 import random
      5 import types
      6 import locks
      7 import utillib
      8 from carbon.common.script.sys.service import Service
      9 from carbon.common.script.sys.serviceConst import ROLE_CONTENT
     10 from carbon.common.script.util.commonutils import GetAttrs
     11 from carbonui.util.various_unsorted import SortListOfTuples
     12 from eve.client.script.paperDoll.paperDollSculpting import PaperDollSculpting
     13 from eve.client.script.paperDoll.paperDollSpawnWrappers import PaperDollManager
     14 from eve.client.script.ui.login.charcreation.eveDollRandomizer import EveDollRandomizer
     15 from eve.common.script.util import paperDollUtil
     16 import log
     17 import blue
     18 import charactercreator.const as ccConst
     19 import eve.client.script.ui.login.charcreation.ccUtil as ccUtil
     20 import telemetry
     21 import geo2
     22 import uthread
     23 import evegraphics.settings as gfxsettings
     24 from carbonui.uicore import uicore
     25 from eveprefs import prefs, boot
     26 import charactercreator.client.animparams as animparams
     27 from eve.common.script.paperDoll.paperDollDefinitions import BODY_CATEGORIES, ConvertDNAForDB, DOLL_EXTRA_PARTS, GENDER, HAIR_CATEGORIES, HEAD_CATEGORIES, ModifierRow, SEPERATOR_CHAR, LOD_SKIN
     28 from eve.common.script.util.paperDollBloodLineAssets import bloodlineAssets
     29 WEIGHT_MODIFIER_PATH = ['bodyshapes/fatshape']
     30 MUSCLE_MODIFIER_PATH = ['bodyshapes/muscularshape']
     31 POTBELLY_PATH = ['bodyshapes/abs_forwardshape']
     32 THIN_MODIFIER_PATH = ['bodyshapes/thinshape']
     33 CATEGORY_NAMES = [HEAD_CATEGORIES.FACEMODIFIERS, DOLL_EXTRA_PARTS.BODYSHAPES, DOLL_EXTRA_PARTS.UTILITYSHAPES]
     34 WEIGHT_BREAK_DOWNS = [('weightUpDown', 'up', 'down'), ('weightLeftRight', 'left', 'right'), ('weightForwardBack', 'forward', 'back')]
     35 
     36 class Character(Service):
     37     __update_on_reload__ = 1
     38     __guid__ = 'svc.character'
     39     __exportedcalls__ = {}
     40     __notifyevents__ = ['OnGraphicSettingsChanged']
     41     __dependencies__ = ['cacheDirectoryLimit']
     42 
     43     @telemetry.ZONE_METHOD
     44     def __init__(self):
     45         super(Character, self).__init__()
     46         self.characters = {}
     47         self.characterMetadata = {}
     48         self.characterScenes = {}
     49         self.index = 0
     50         self.sculpting = None
     51         self.sculptingActive = False
     52         self.scene = None
     53         self.animationBloodlineData = None
     54         self.preloadedResources = []
     55         self.cachedHairColorVariations = {}
     56         self.cachedEyeshadowColorVariations = {}
     57         self.cachedHeadTattooColorVariations = {}
     58         self.cachedPortraitResourcesByPath = None
     59         self.baseHairColors = None
     60         self.tuckingOptions = {}
     61         for k, (modifierName, locationName, subKey) in ccConst.TUCKMAPPING.iteritems():
     62             self.tuckingOptions[modifierName] = subKey
     63 
     64         self.assetsToIDs = {'male': None,
     65          'female': None}
     66         self.textureQuality = 1
     67         self.cachedPortraitInfo = {}
     68         self.modifierLocationsByName = {}
     69         self.modifierLocationsByKey = {}
     70 
     71     def lazyprop(fn):
     72         attr_name = '_lazy_' + fn.__name__
     73 
     74         @property
     75         def _lazyprop(self):
     76             if not hasattr(self, attr_name):
     77                 setattr(self, attr_name, fn(self))
     78             return getattr(self, attr_name)
     79 
     80         return _lazyprop
     81 
     82     @lazyprop
     83     def factory(self):
     84         factory = sm.GetService('paperDollClient').dollFactory
     85         factory.clothSimulationActive = False
     86         return factory
     87 
     88     @lazyprop
     89     def paperDollManager(self):
     90         pdm = PaperDollManager(self.factory)
     91         return pdm
     92 
     93     def PopulateModifierLocationDicts(self, force = False):
     94         if not force and len(self.modifierLocationsByKey) > 0:
     95             return
     96         self.modifierLocationsByName = {}
     97         self.modifierLocationsByKey = {}
     98         for row in cfg.paperdollModifierLocations:
     99             self.modifierLocationsByKey[row.modifierLocationID] = row.modifierKey
    100             self.modifierLocationsByName[row.modifierKey] = row.modifierLocationID
    101 
    102     @telemetry.ZONE_METHOD
    103     def OnGraphicSettingsChanged(self, changes):
    104         if gfxsettings.GFX_TEXTURE_QUALITY in changes:
    105             if len(self.characters):
    106                 dollID = self.characters.keys()[0]
    107                 pdc = self.characters[dollID]
    108                 pdc.doll.buildDataManager.SetAllAsDirty()
    109                 pdc.Update()
    110         if gfxsettings.GFX_CHAR_TEXTURE_QUALITY in changes:
    111             textureQuality = gfxsettings.Get(gfxsettings.GFX_CHAR_TEXTURE_QUALITY)
    112             self.SetTextureQuality(textureQuality)
    113         if gfxsettings.GFX_SHADER_QUALITY in changes:
    114             if len(self.characters):
    115                 dollID = self.characters.keys()[0]
    116                 pdc = self.characters[dollID]
    117                 pdc.doll.useFastShader = False
    118         if self.sculpting is not None:
    119             self.sculpting.SetupPickScene(doUpdate=False)
    120 
    121     @telemetry.ZONE_METHOD
    122     def PreloadItems(self, gender, items):
    123         return
    124         self.preloadedResources = []
    125         if gender == ccConst.GENDERID_FEMALE:
    126             options = self.factory.femaleOptions
    127         elif gender == ccConst.GENDERID_MALE:
    128             options = self.factory.maleOptions
    129         else:
    130             log.LogWarn('Preloading failed. Invalid gender!')
    131             return
    132         resources = []
    133         for cat in options:
    134             for item in items:
    135                 if item in cat:
    136                     resources.append(options[cat])
    137 
    138         for paths in resources:
    139             for path in paths:
    140                 try:
    141                     whitelist = ['.png', '.dds', '.gr2']
    142                     if path.lower()[-4:] in whitelist:
    143                         self.preloadedResources.append(blue.resMan.GetResource(path))
    144                 except:
    145                     log.LogException()
    146 
    147     @telemetry.ZONE_METHOD
    148     def ClearPreloads(self):
    149         self.preLoadedResources = []
    150 
    151     def InitializeNewCharacter(self, charID, genderID, bloodlineID, raceID):
    152         plugList = self.GetAvailableTypesByCategory('makeup/implants', genderID, raceID)
    153         if len(plugList) > 0:
    154             self.ApplyTypeToDoll(charID, plugList[0], doUpdate=False)
    155         self.EnsureUnderwear(charID, genderID, raceID)
    156         if genderID == ccConst.GENDERID_MALE:
    157             self.ApplyTypeToDoll(charID, self.GetRelativePath(ccConst.BASE_HAIR_TYPE_MALE), doUpdate=False)
    158         else:
    159             self.ApplyTypeToDoll(charID, self.GetRelativePath(ccConst.BASE_HAIR_TYPE_FEMALE), doUpdate=False)
    160         self.SetInitialSkintype(charID, genderID, bloodlineID, raceID)
    161         self.RandomizeDollCategory(charID, raceID, ccConst.eyes, 0)
    162         self.RandomizeDollCategory(charID, raceID, ccConst.eyebrows, 0)
    163         self.RandomizeDollCategory(charID, raceID, ccConst.hair, 0)
    164         self.RandomizeDollCategory(charID, raceID, ccConst.outer, 0)
    165         self.RandomizeDollCategory(charID, raceID, ccConst.topmiddle, 0)
    166         self.RandomizeDollCategory(charID, raceID, ccConst.bottomouter, 0)
    167         self.RandomizeDollCategory(charID, raceID, ccConst.feet, 0)
    168         p, s = self.GetAvailableColorsForCategory('hair', genderID, raceID)
    169         primary = random.choice(p)
    170         secondary = random.choice(s)
    171         self.SetColorValueByCategory(charID, 'hair', primary, secondary, doUpdate=False)
    172         self.SetHairDarkness(charID, 0.5)
    173         self.SynchronizeHairColors(charID)
    174 
    175     def GetNewCharacterMetadata(self, genderID, bloodlineID):
    176         return utillib.KeyVal(genderID=genderID, bloodlineID=bloodlineID, types={}, typeColors={}, typeWeights={}, typeSpecularity={}, hairDarkness=0.0)
    177 
    178     def GetCharacterMetadataByCategory(self, charID, category):
    179         type_meta = {}
    180         if category in self.characterMetadata[charID].types:
    181             type_meta['types'] = self.characterMetadata[charID].types[category]
    182             type_meta['typeColors'] = self.characterMetadata[charID].typeColors.get(category)
    183             type_meta['typeSpecularity'] = self.characterMetadata[charID].typeSpecularity.get(category)
    184             type_meta['typeWeights'] = self.characterMetadata[charID].typeWeights.get(category)
    185         return type_meta
    186 
    187     def SetCharacterMetadataByCategory(self, charID, category, type_meta):
    188         if type_meta:
    189             self.characterMetadata[charID].types[category] = type_meta['types']
    190             if type_meta['typeColors']:
    191                 self.characterMetadata[charID].typeColors[category] = type_meta['typeColors']
    192             if type_meta['typeSpecularity']:
    193                 self.characterMetadata[charID].typeSpecularity[category] = type_meta['typeSpecularity']
    194             if type_meta['typeWeights']:
    195                 self.characterMetadata[charID].typeWeights[category] = type_meta['typeWeights']
    196 
    197     def RemoveFromCharacterMetadata(self, charID, category):
    198         if category in self.characterMetadata[charID].types:
    199             self.characterMetadata[charID].types.pop(category, None)
    200             self.characterMetadata[charID].typeColors.pop(category, None)
    201             self.characterMetadata[charID].typeSpecularity.pop(category, None)
    202             self.characterMetadata[charID].typeWeights.pop(category, None)
    203 
    204     @telemetry.ZONE_METHOD
    205     def AddCharacterToScene(self, charID, scene, gender, bloodlineID = None, raceID = None, dna = None, position = (0.0, 0.0, 0.0), updateDoll = True, textureResolution = None, lod = None, noRandomize = False, anim = True):
    206         applyNewCharacterTypes = False
    207         if not noRandomize:
    208             noRandomize = prefs.GetValue('NoRandomize', 0)
    209         name = 'doll_%s' % charID
    210         if charID in self.characters:
    211             character = self.characters[charID]
    212             if hasattr(character, 'MoveToScene'):
    213                 character.MoveToScene(scene)
    214             else:
    215                 doll = character.doll
    216                 self.characters[charID] = character = self.paperDollManager.SpawnDoll(scene, doll=doll, updateDoll=updateDoll, point=position, name=name)
    217         elif dna is not None:
    218             randomDoll = paperDollUtil.CreateRandomDollNoClothes(gender, bloodlineID, raceID, noRandomize=True)
    219             self.characters[charID] = character = self.paperDollManager.SpawnDoll(scene, doll=randomDoll, updateDoll=updateDoll, point=position, name=name)
    220         else:
    221             applyNewCharacterTypes = True
    222             randomDoll = paperDollUtil.CreateRandomDollNoClothes(gender, bloodlineID, raceID, noRandomize=noRandomize)
    223             self.characters[charID] = character = self.paperDollManager.SpawnDoll(scene, doll=randomDoll, updateDoll=updateDoll, point=position, name=name)
    224         self.characterMetadata[charID] = self.GetNewCharacterMetadata(ccUtil.PaperDollGenderToGenderID(gender), bloodlineID)
    225         needUpdate = False
    226         if character is not None:
    227             if lod is not None:
    228                 character.doll.overrideLod = lod
    229                 needUpdate = True
    230             if textureResolution is not None:
    231                 character.doll.textureResolution = textureResolution
    232                 needUpdate = True
    233         self.characterScenes[charID] = scene
    234         self.scene = scene
    235         if character is None:
    236             log.LogError('AddCharacterToScene: Character', charID, 'not created')
    237             return
    238         avatar = character.avatar
    239         if anim:
    240             network = animparams.GetParamsPerAvatar(avatar, charID)
    241             network.Rebuild()
    242         if applyNewCharacterTypes and not noRandomize:
    243             self.InitializeNewCharacter(charID, ccUtil.PaperDollGenderToGenderID(gender), bloodlineID, raceID)
    244         elif dna is not None:
    245             if type(dna) is list:
    246                 self.MatchDNA(character, dna)
    247             else:
    248                 self.ApplyDBRowToDoll(charID, gender, bloodlineID, raceID, dna)
    249             needUpdate = True
    250         if needUpdate and updateDoll:
    251             self.UpdateDoll(charID)
    252         return character
    253 
    254     def spawn_character_in_scene(self, charID, scene, gender, bloodlineID = None, raceID = None, dna = None, lod = LOD_SKIN):
    255         if charID in self.characters:
    256             log.LogWarn('Character already exists in scene: %s' % charID)
    257             return
    258         randomDoll = paperDollUtil.CreateRandomDollNoClothes(gender, bloodlineID, raceID, noRandomize=True)
    259         char_doll = self.paperDollManager.SpawnDoll(scene, doll=randomDoll, name='doll_%s' % charID, updateDoll=False, point=(0.0, 0.0, 0.0))
    260         self.characters[charID] = char_doll
    261         self.characterScenes[charID] = scene
    262         self.characterMetadata[charID] = self.GetNewCharacterMetadata(ccUtil.PaperDollGenderToGenderID(gender), bloodlineID)
    263         network = animparams.GetParamsPerAvatar(char_doll.avatar, charID)
    264         network.Rebuild()
    265         char_doll.doll.overrideLod = lod
    266         if dna:
    267             if type(dna) is list:
    268                 self.MatchDNA(char_doll, dna)
    269             else:
    270                 self.ApplyDBRowToDoll(charID, gender, bloodlineID, raceID, dna)
    271         return char_doll
    272 
    273     def spawn_random_character_in_scene(self, charID, scene, gender, bloodlineID = None, raceID = None, lod = LOD_SKIN):
    274         if charID in self.characters:
    275             log.LogWarn('Character already exists in scene: %s' % charID)
    276             return
    277         random_doll = paperDollUtil.CreateRandomDollNoClothes(gender, bloodlineID, raceID, noRandomize=False)
    278         char_doll = self.paperDollManager.SpawnDoll(scene, doll=random_doll, name='doll_%s' % charID, updateDoll=False, point=(0.0, 0.0, 0.0))
    279         self.characters[charID] = char_doll
    280         self.characterScenes[charID] = scene
    281         self.characterMetadata[charID] = self.GetNewCharacterMetadata(ccUtil.PaperDollGenderToGenderID(gender), bloodlineID)
    282         network = animparams.GetParamsPerAvatar(char_doll.avatar, charID)
    283         network.Rebuild()
    284         char_doll.doll.overrideLod = lod
    285         return char_doll
    286 
    287     @telemetry.ZONE_METHOD
    288     def RemoveCharacter(self, charID):
    289         if charID not in self.characters:
    290             return
    291         avatar = self.characters[charID].avatar
    292         scene = self.characterScenes.get(charID, self.scene)
    293         if scene:
    294             if avatar and avatar in scene.dynamics:
    295                 scene.dynamics.remove(avatar)
    296         paperDollCharacter = self.characters.pop(charID)
    297         self.characterScenes.pop(charID, None)
    298         self.paperDollManager.RemovePaperDollCharacter(paperDollCharacter)
    299         del paperDollCharacter
    300         del self.characterMetadata[charID]
    301 
    302     @telemetry.ZONE_METHOD
    303     def CleanCircularReferences(self):
    304         for each in self.characters:
    305             character = self.characters[each]
    306             if character.avatar:
    307                 for cs in character.avatar.curveSets:
    308                     for bind in cs.bindings:
    309                         bind.copyValueCallable = None
    310 
    311                     del cs.bindings[:]
    312                     del cs.curves[:]
    313 
    314                 del character.avatar.curveSets[:]
    315 
    316     @telemetry.ZONE_METHOD
    317     def TearDown(self):
    318         self.preloadedResources = []
    319         self.paperDollManager.ClearDolls()
    320         self.CleanCircularReferences()
    321         self.characters = {}
    322         self.characterScenes = {}
    323         sceneManager = sm.GetService('sceneManager')
    324         sceneManager.UnregisterScene('characterCreation')
    325         if self.sculpting is not None:
    326             self.sculpting.avatar = None
    327             if self.sculpting.highlightGhost is not None:
    328                 self.sculpting.highlightGhost.avatar = None
    329             if self.sculpting.bodyHighlightGhost is not None:
    330                 self.sculpting.bodyHighlightGhost.avatar = None
    331             self.sculpting.scene = None
    332             self.sculpting.pickScene = None
    333             self.sculpting = None
    334         self.scene = None
    335 
    336     @telemetry.ZONE_METHOD
    337     def GetModifierByPath(self, charID, path):
    338         if charID in self.characters:
    339             doll = self.characters[charID].doll
    340             return doll.GetBuildDataByResPath(path, includeFuture=True)
    341 
    342     @telemetry.ZONE_METHOD
    343     def GetModifiersByCategory(self, charID, category):
    344         if charID not in self.characters:
    345             return None
    346         doll = self.characters[charID].doll
    347         ret = []
    348         mods = doll.buildDataManager.GetModifiersAsList(includeFuture=True)
    349         for m in mods:
    350             resPath = m.GetResPath()
    351             resPathSplit = resPath.split('/')
    352             categSplit = category.split('/')
    353             match = True
    354             for i, each in enumerate(categSplit):
    355                 if not (len(resPathSplit) > i and resPathSplit[i] == each):
    356                     match = False
    357                     break
    358 
    359             if match:
    360                 ret.append(m)
    361 
    362         return ret
    363 
    364     @telemetry.ZONE_METHOD
    365     def UpdateDoll(self, charID, fromWhere = None, registerDna = True, forceUpdate = False):
    366         if charID not in self.characters:
    367             return
    368         doll = self.characters[charID].doll
    369         avatar = self.characters[charID].avatar
    370         if self.sculpting:
    371             self.sculpting.UpdateBlendShapes([])
    372             if forceUpdate:
    373                 doll.Update(self.factory, avatar)
    374         else:
    375             doll.Update(self.factory, avatar)
    376 
    377         def wait_t():
    378             while doll.IsBusyUpdating():
    379                 blue.synchro.Yield()
    380 
    381             if registerDna and hasattr(uicore, 'layer') and hasattr(uicore.layer, 'charactercreation'):
    382                 uicore.layer.charactercreation.controller.TryStoreDna(doll.lastUpdateRedundant, fromWhere)
    383             sm.ScatterEvent('OnDollUpdated', charID, doll.lastUpdateRedundant, fromWhere)
    384             if hasattr(uicore, 'layer') and hasattr(uicore.layer, 'charactercreation'):
    385                 uicore.layer.charactercreation.controller.UnlockNavigation()
    386 
    387         if hasattr(uicore, 'layer') and hasattr(uicore.layer, 'charactercreation') and uicore.layer.charactercreation.isopen:
    388             uthread.new(wait_t)
    389 
    390     @telemetry.ZONE_METHOD
    391     def UpdateTattoos(self, charID, doUpdate = True, paperdoll = None):
    392         if charID not in self.characters and not paperdoll:
    393             return
    394         if paperdoll is None:
    395             pdc = self.characters[charID]
    396         else:
    397             pdc = paperdoll
    398 
    399         def fun_t():
    400             while pdc.doll.IsBusyUpdating():
    401                 blue.synchro.Yield()
    402 
    403             tattooModifiers = pdc.doll.buildDataManager.GetModifiersByCategory(BODY_CATEGORIES.TATTOO)
    404             if tattooModifiers:
    405                 for each in tattooModifiers:
    406                     each.IsDirty = True
    407 
    408             if doUpdate:
    409                 pdc.Update()
    410 
    411         uthread.new(fun_t)
    412 
    413     @telemetry.ZONE_METHOD
    414     def SetDollBloodline(self, charID, bloodlineID):
    415         self.characterMetadata[charID].bloodlineID = bloodlineID
    416         doll = self.characters[charID].doll
    417         gender = doll.gender
    418         limitPath = 'res:/Graphics/Character/Global/FaceSetup/ScultpingLimits/' + bloodlineAssets[bloodlineID] + '_' + gender + '_blendshape_limits.yaml'
    419         doll.buildDataManager.AddBlendshapeLimitsFromFile(limitPath)
    420         self.AdaptDollAnimationData(bloodlineAssets[bloodlineID], self.characters[charID].avatar, gender)
    421 
    422     @telemetry.ZONE_METHOD
    423     def ResetDoll(self, charID, bloodlineID = None):
    424         if bloodlineID is not None:
    425             self.SetDollBloodline(charID, bloodlineID)
    426         if self.characterMetadata[charID].bloodlineID:
    427             self.ApplyItemToDoll(charID, 'head', bloodlineAssets[self.characterMetadata[charID].bloodlineID], doUpdate=True)
    428 
    429     @telemetry.ZONE_METHOD
    430     def AdaptDollAnimationData(self, bloodline, avatar, gender):
    431         if not self.animationBloodlineData:
    432             animPath = 'res:/Graphics/Character/Global/FaceSetup/AnimationData.yaml'
    433             self.animationBloodlineData = ccUtil.LoadFromYaml(animPath)
    434         network = animparams.GetParamsPerAvatar(avatar)
    435         for param in self.animationBloodlineData[bloodline][gender]:
    436             try:
    437                 paramName = 'ControlParameters|' + param
    438                 network.SetControlParameter(paramName, self.animationBloodlineData[bloodline][gender][param])
    439             except:
    440                 log.LogWarn('Invalid control parameter for bloodline: ' + param)
    441 
    442     @telemetry.ZONE_METHOD
    443     def SetCharacterWeight(self, charID, weight, doUpdate = True):
    444         doll = self.characters[charID].doll
    445         avatar = self.characters[charID].avatar
    446         network = animparams.GetParamsPerAvatar(avatar, charID)
    447         if network is not None:
    448             network.SetControlParameter('ControlParameters|WeightAdjustment', weight)
    449         for mod in THIN_MODIFIER_PATH:
    450             limit = doll.buildDataManager.modifierLimits.get(mod.split('/')[-1])
    451             multiplier = 1.0
    452             if limit and len(limit) == 2:
    453                 multiplier = limit[1]
    454             modifier = self.GetModifierByPath(charID, mod)
    455             if modifier is None:
    456                 modifier = doll.AddResource(mod, 0.0, self.factory)
    457             if weight <= 0.5:
    458                 modifier.weight = (1 - weight * 2.0) * multiplier
    459             else:
    460                 modifier.weight = 0.0
    461 
    462         for mod in WEIGHT_MODIFIER_PATH:
    463             limit = doll.buildDataManager.modifierLimits.get(mod.split('/')[-1])
    464             multiplier = 1.0
    465             if limit and len(limit) == 2:
    466                 multiplier = limit[1]
    467             if weight >= 0.5:
    468                 weight = (weight - 0.5) * 2.0 * multiplier
    469             else:
    470                 weight = 0.0
    471             modifier = self.GetModifierByPath(charID, mod)
    472             if modifier is None:
    473                 modifier = doll.AddResource(mod, weight, self.factory)
    474             else:
    475                 modifier.weight = weight
    476 
    477         if doUpdate:
    478             self.UpdateTattoos(charID, doUpdate=False)
    479             self.UpdateDoll(charID, fromWhere='SetCharacterWeight')
    480 
    481     @telemetry.ZONE_METHOD
    482     def GetCharacterWeight(self, charID):
    483         doll = self.characters[charID].doll
    484         avatar = self.characters[charID].avatar
    485         ret = []
    486         for mod in WEIGHT_MODIFIER_PATH:
    487             limit = doll.buildDataManager.modifierLimits.get(mod.split('/')[-1])
    488             multiplier = 1.0
    489             if limit and len(limit) == 2:
    490                 multiplier = limit[1]
    491             modifier = self.GetModifierByPath(charID, mod)
    492             if modifier is not None:
    493                 if modifier.weight > 0.0:
    494                     return 0.5 + modifier.weight * 0.5 / multiplier
    495 
    496         for mod in THIN_MODIFIER_PATH:
    497             limit = doll.buildDataManager.modifierLimits.get(mod.split('/')[-1])
    498             multiplier = 1.0
    499             if limit and len(limit) == 2:
    500                 multiplier = limit[1]
    501             modifier = self.GetModifierByPath(charID, mod)
    502             if modifier is not None:
    503                 return (1 - modifier.weight / multiplier) * 0.5
    504 
    505         return 0.5
    506 
    507     @telemetry.ZONE_METHOD
    508     def SetCharacterMuscularity(self, charID, weight, doUpdate = True):
    509         doll = self.characters[charID].doll
    510         avatar = self.characters[charID].avatar
    511         for mod in MUSCLE_MODIFIER_PATH:
    512             limit = doll.buildDataManager.modifierLimits.get(mod.split('/')[-1])
    513             multiplier = 1.0
    514             if limit and len(limit) == 2:
    515                 multiplier = limit[1]
    516             modifier = self.GetModifierByPath(charID, mod)
    517             if modifier is None:
    518                 modifier = doll.AddResource(mod, 0.0, self.factory)
    519             modifier.weight = weight * multiplier
    520 
    521         if doUpdate:
    522             self.UpdateTattoos(charID, doUpdate=False)
    523             self.UpdateDoll(charID, fromWhere='SetCharacterMuscularity')
    524 
    525     @telemetry.ZONE_METHOD
    526     def GetCharacterMuscularity(self, charID):
    527         doll = self.characters[charID].doll
    528         avatar = self.characters[charID].avatar
    529         ret = []
    530         for mod in MUSCLE_MODIFIER_PATH:
    531             limit = doll.buildDataManager.modifierLimits.get(mod.split('/')[-1])
    532             multiplier = 1.0
    533             if limit and len(limit) == 2:
    534                 multiplier = limit[1]
    535             modifier = self.GetModifierByPath(charID, mod)
    536             if modifier is not None:
    537                 ret = modifier.weight / multiplier
    538             else:
    539                 ret = 0.0
    540 
    541         return ret
    542 
    543     @telemetry.ZONE_METHOD
    544     def GetModifierByCategory(self, charID, category, getAll = False):
    545         modifiers = self.GetModifiersByCategory(charID, category)
    546         if not modifiers:
    547             return None
    548         elif getAll:
    549             return modifiers
    550         else:
    551             if category == HAIR_CATEGORIES.BEARD:
    552                 for modifier in modifiers:
    553                     if modifier.name != 'stubble':
    554                         return modifier
    555 
    556             return modifiers[0]
    557 
    558     @telemetry.ZONE_METHOD
    559     def GetEyeshadowColors(self, genderID):
    560         colorVars = self.LoadEyeShadowColorVariations(genderID)
    561         return self.GetModifierColors(colorVars=colorVars)
    562 
    563     @telemetry.ZONE_METHOD
    564     def LoadEyeShadowColorVariations(self, genderID):
    565         if genderID != ccConst.GENDERID_FEMALE:
    566             log.LogError('LoadEyeShadowColorVariations - can only use this for females')
    567             return []
    568         cached = self.cachedEyeshadowColorVariations.get(genderID)
    569         if cached is not None:
    570             return cached
    571         variations = self.GetModifierColorVariation(ccConst.EYESHADOWCOLORS, ccConst.EYESHADOWCOLOR_PATHS[genderID])
    572         self.cachedEyeshadowColorVariations[genderID] = variations
    573         return variations
    574 
    575     @telemetry.ZONE_METHOD
    576     def GetHairColors(self, genderID):
    577         colorVars = self.LoadHairColorVariations(genderID)
    578         if self.baseHairColors is None:
    579             self.baseHairColors = [ccUtil.LoadFromYaml(ccConst.BASE_HAIR_COLOR_FEMALE), ccUtil.LoadFromYaml(ccConst.BASE_HAIR_COLOR_MALE)]
    580         baseColor = self.baseHairColors[genderID]
    581         if baseColor is None:
    582             log.LogWarn('Failed to load base color for hair')
    583             baseColor = (0.5, 0.5, 0.5, 1.0)
    584         return self.GetModifierColors(colorVars=colorVars)
    585 
    586     @telemetry.ZONE_METHOD
    587     def LoadHairColorVariations(self, genderID):
    588         if genderID is None:
    589             log.LogError('LoadHairColorVariations - genderID must not be None')
    590             return []
    591         cached = self.cachedHairColorVariations.get(genderID)
    592         if cached is not None:
    593             return cached
    594         variations = self.GetModifierColorVariation(ccConst.HAIRCOLORS, ccConst.HAIRCOLOR_PATHS[genderID])
    595         self.cachedHairColorVariations[genderID] = variations
    596         return variations
    597 
    598     def GetHeadTattooColors(self, genderID):
    599         colorVars = self.LoadHeadTattooVariations(genderID)
    600         return self.GetModifierColors(colorVars=colorVars)
    601 
    602     @telemetry.ZONE_METHOD
    603     def LoadHeadTattooVariations(self, genderID):
    604         if genderID is None:
    605             log.LogError('LoadHeadTattooVariations - genderID must not be None')
    606             return []
    607         cached = self.cachedHeadTattooColorVariations.get(genderID)
    608         if cached is not None:
    609             return cached
    610         variations = self.GetModifierColorVariation(ccConst.HEADTATTOOCOLORS, ccConst.HEADTATTOOCOLOR_PATHS[genderID])
    611         self.cachedHeadTattooColorVariations[genderID] = variations
    612         return variations
    613 
    614     @telemetry.ZONE_METHOD
    615     def GetModifierColorVariation(self, colors, path):
    616         variations = {}
    617         for each in colors:
    618             colorFile = path + each
    619             var = ccUtil.LoadFromYaml(colorFile)
    620             if var is not None:
    621                 variations[each.split('.')[0]] = var
    622             else:
    623                 log.LogWarn('GetModifierColorVariation - Could not load color file:', colorFile)
    624 
    625         return variations
    626 
    627     def GetModifierColors(self, colorVars):
    628         retColorsA = []
    629         retColorsBC = []
    630         for each in colorVars:
    631             color = colorVars[each]['colors']
    632             if color and type(color[0]) == types.TupleType:
    633                 if each.lower().endswith('_a'):
    634                     displayColor = color[0]
    635                     r, g, b, a = displayColor
    636                     yiq = colorsys.rgb_to_yiq(r, g, b)
    637                     retColorsA.append((yiq, (each, (r,
    638                        g,
    639                        b,
    640                        1.0), colorVars[each])))
    641                 elif each.lower().endswith('_bc'):
    642                     displayColor = color[1]
    643                     r, g, b, a = displayColor
    644                     yiq = colorsys.rgb_to_yiq(r, g, b)
    645                     retColorsBC.append((yiq, (each, (r,
    646                        g,
    647                        b,
    648                        1.0), colorVars[each])))
    649                 else:
    650                     print('Unsupported modifier color', each)
    651 
    652         retColorsA = SortListOfTuples(retColorsA)
    653         retColorsBC = SortListOfTuples(retColorsBC)
    654         return (retColorsA, retColorsBC)
    655 
    656     def SetHairDarkness(self, charID, darkness):
    657         if darkness != self.characterMetadata[charID].hairDarkness:
    658             self.characterMetadata[charID].hairDarkness = darkness
    659             self.SynchronizeHairColors(charID)
    660 
    661     def GetHairDarkness(self, charID):
    662         return self.characterMetadata[charID].hairDarkness
    663 
    664     @telemetry.ZONE_METHOD
    665     def SynchronizeHairColors(self, charID):
    666         hairData = self.GetModifierByCategory(charID, ccConst.hair)
    667         if hairData is not None:
    668             colorizeData = hairData.colorizeData
    669             orgA, orgB, orgC = colorizeData
    670             newA = geo2.Vector(*orgA)
    671             lowA = newA * 0.2
    672             newA = geo2.Vec4Lerp(lowA, newA, self.characterMetadata[charID].hairDarkness)
    673             adjustedColor = (newA, orgB, orgC)
    674             for hairModifier in (ccConst.beard, ccConst.eyebrows):
    675                 if hairModifier == ccConst.beard:
    676                     hms = self.GetModifierByCategory(charID, hairModifier, getAll=True)
    677                 else:
    678                     hms = [self.GetModifierByCategory(charID, hairModifier)]
    679                 if hms is None:
    680                     continue
    681                 for hm in hms:
    682                     if hm:
    683                         hm.colorizeData = adjustedColor
    684                         hm.pattern = hairData.pattern
    685                         hm.patternData = hairData.patternData
    686                         hm.specularColorData = hairData.specularColorData
    687                         hm.SetColorVariation('none')
    688 
    689     @telemetry.ZONE_METHOD
    690     def GetAvailableColorsForCategory(self, categoryPath, genderID, raceID):
    691         if categoryPath == ccConst.hair:
    692             return self.GetHairColors(genderID)
    693         if categoryPath == ccConst.t_head:
    694             return self.GetHeadTattooColors(genderID)
    695         if genderID == ccConst.GENDERID_FEMALE and categoryPath == ccConst.eyeshadow:
    696             return self.GetEyeshadowColors(genderID)
    697         if type(genderID) is int:
    698             genderID = ccUtil.GenderIDToPaperDollGender(genderID)
    699         combined = {}
    700         typeData = self.GetAvailableTypesByCategory(categoryPath, genderID, raceID, getTypesOnly=True)
    701         for each in typeData:
    702             modifier = self.factory.CollectBuildData(genderID, each[0])
    703             combined.update(modifier.colorVariations)
    704 
    705         doneA = []
    706         doneBC = []
    707         retColorsA = []
    708         retColorsBC = []
    709         for colorName, colorData in combined.iteritems():
    710             if categoryPath.startswith(ccConst.lipstick):
    711                 if colorName.find('_glossy') != -1 or colorName.find('_medium') != -1:
    712                     continue
    713             displayColor = colorData.get('colors') if colorData else None
    714             if displayColor is None:
    715                 log.LogWarn('No colors in colorData when calling GetAvailableColorsForCategory', categoryPath, genderID)
    716                 continue
    717             if type(displayColor) == types.TupleType or type(displayColor) == types.ListType:
    718                 displayColor = colorData['colors'][0]
    719                 r, g, b, a = displayColor
    720             else:
    721                 r, g, b = displayColor
    722             h, s, v = colorsys.rgb_to_hsv(r, g, b)
    723             r, g, b = colorsys.hsv_to_rgb(h, s * 0.8, v * 0.8)
    724             yiq = colorsys.rgb_to_yiq(r, g, b)
    725             if colorName.lower().endswith('_bc'):
    726                 if colorData['colors'] not in doneBC:
    727                     retColorsBC.append((yiq, (colorName, (r,
    728                        g,
    729                        b,
    730                        1.0), colorData)))
    731                     doneBC.append(colorData['colors'])
    732             elif colorData['colors'] not in doneA:
    733                 retColorsA.append((yiq, (colorName, (r,
    734                    g,
    735                    b,
    736                    1.0), colorData)))
    737                 doneA.append(colorData['colors'])
    738 
    739         retColorsA = SortListOfTuples(retColorsA)
    740         retColorsBC = SortListOfTuples(retColorsBC)
    741         return (retColorsA, retColorsBC)
    742 
    743     @telemetry.ZONE_METHOD
    744     def GetCharacterColorVariations(self, charID, category):
    745         colors = []
    746         selectedIndex = None
    747         modifier = self.GetModifierByCategory(charID, category)
    748         if modifier:
    749             colorVars = modifier.GetColorVariations()
    750             if category == ccConst.skintone:
    751                 bloodline = self.characterMetadata[charID].bloodlineID
    752                 bloodlineFilter = {1: 'deteis',
    753                  2: 'civire',
    754                  11: 'achura',
    755                  7: 'gallente',
    756                  8: 'intaki',
    757                  12: 'jinmei',
    758                  3: 'sebiestor',
    759                  4: 'brutor',
    760                  14: 'vherokior',
    761                  5: 'amarr',
    762                  13: 'khanid',
    763                  6: 'nikunni'}
    764                 filter = bloodlineFilter[bloodline]
    765                 newColorVars = []
    766                 for c in colorVars:
    767                     if c.startswith(filter):
    768                         newColorVars.append(c)
    769 
    770                 colorVars = newColorVars
    771             if category == ccConst.lipstick:
    772                 newColorVars = []
    773                 for c in colorVars:
    774                     if c.lower().endswith('_medium'):
    775                         newColorVars.append(c)
    776 
    777                 colorVars = newColorVars
    778             selectedColor = modifier.GetColorizeData()
    779             for i, cv in enumerate(colorVars):
    780                 c = modifier.GetColorsFromColorVariation(cv)
    781                 if selectedColor == c:
    782                     selectedIndex = i
    783                 colors.append((cv, c))
    784 
    785         return (colors, selectedIndex)
    786 
    787     def PrimePortraitResourceCache(self):
    788         with locks.TempLock('PrimePortraitResourceCache'):
    789             if self.cachedPortraitResourcesByPath is not None:
    790                 return
    791             cacheDict = {}
    792             for eachResource in cfg.paperdollPortraitResources.rows:
    793                 cacheDict[eachResource.resPath.lower()] = eachResource
    794 
    795             self.cachedPortraitResourcesByPath = cacheDict
    796 
    797     def GetAvailableBackgrounds(self):
    798         return self.GetAvailablePortraitResources('background')
    799 
    800     def GetAvailablePortraitResources(self, categoryName):
    801         availableTypeIDs = sm.GetService('cc').GetMyApparel()
    802         ret = []
    803         for eachResource in cfg.paperdollPortraitResources.rows:
    804             if eachResource.resourceCategoryID != categoryName:
    805                 continue
    806             if eachResource.typeID is None or eachResource.typeID in availableTypeIDs:
    807                 ret.append(eachResource)
    808 
    809         return ret
    810 
    811     def GetPortraitResourceByPath(self, path):
    812         self.PrimePortraitResourceCache()
    813         path = path.lower()
    814         return self.cachedPortraitResourcesByPath.get(path, None)
    815 
    816     @telemetry.ZONE_METHOD
    817     def GetAvailableTypesByCategory(self, category, gender, raceID, getTypesOnly = False, forceAllTypes = False):
    818         if type(gender) is int:
    819             gender = ccUtil.GenderIDToPaperDollGender(gender)
    820         types = self.factory.ListTypes(gender, category)
    821         ret = []
    822         retData = []
    823         resFile = blue.ResFile()
    824         availableTypeIDs = sm.GetService('cc').GetMyApparel()
    825         hasContentRole = session.role & ROLE_CONTENT or forceAllTypes
    826         defined_fsd_type_paths = self.GetDefinedFSDTypes()
    827         initialCreation = GetAttrs(uicore, 'layer', 'charactercreation', 'mode') == ccConst.MODE_FULLINITIAL_CUSTOMIZATION
    828         for i, each in enumerate(types):
    829             typeData = self.factory.GetItemType(each, gender=gender)
    830             if typeData is None:
    831                 log.LogWarn('GetItemType for path returned None', each)
    832                 continue
    833             assetID, assetTypeID = self.GetAssetAndTypeIDsFromPath(gender, each)
    834             path_parts = each.lower().split(category)
    835             non_fsd_resource = category + category.join(path_parts[1:]) not in defined_fsd_type_paths
    836             if non_fsd_resource:
    837                 log.LogWarn("Item type doesn't exist in FSD: ", each)
    838                 if not hasContentRole:
    839                     continue
    840             if not hasContentRole and assetTypeID is not None and (assetTypeID not in availableTypeIDs or initialCreation):
    841                 continue
    842             if len(typeData) == 4:
    843                 typeData = typeData[:-1]
    844             if not non_fsd_resource:
    845                 fsdData = cfg.paperdollResources.Get(assetID)
    846                 if fsdData:
    847                     empireRestrictions = fsdData.empireRestrictions
    848                     if empireRestrictions is not None and raceID in empireRestrictions:
    849                         pass
    850                     if raceID is None or empireRestrictions is None or empireRestrictions and raceID not in empireRestrictions:
    851                         ret.append(((assetTypeID is None, i), (assetID, typeData, assetTypeID)))
    852                         retData.append(typeData)
    853             else:
    854                 ret.append(((assetTypeID is None, i), (assetID, typeData, assetTypeID)))
    855                 retData.append(typeData)
    856 
    857         if getTypesOnly:
    858             return retData
    859         log.LogNotice('GetAvailableTypesByCategory -> (%s, %s, %s, %s)' % (gender,
    860          category,
    861          raceID,
    862          len(ret)))
    863         ret = SortListOfTuples(ret)
    864         return ret
    865 
    866     @telemetry.ZONE_METHOD
    867     def GetDefinedFSDTypes(self):
    868         type_path_list = []
    869         for resource in cfg.paperdollResources.rows:
    870             type_path_list.append(resource.resPath.lower())
    871 
    872         return type_path_list
    873 
    874     @telemetry.ZONE_METHOD
    875     def GetAvailableItemsByCategory(self, category, gender, bloodline, showVariations = False):
    876         if type(gender) is int:
    877             gender = ccUtil.GenderIDToPaperDollGender(gender)
    878         items = self.factory.ListOptions(gender, category, showVariations)
    879         self.PreloadItems(gender, items)
    880         return items
    881 
    882     @telemetry.ZONE_METHOD
    883     def GetCategoryFromResPath(self, resPath):
    884         parts = resPath.split('/')
    885         return '/'.join(parts[:-1])
    886 
    887     @telemetry.ZONE_METHOD
    888     def ApplyTypeToDoll(self, charID, itemType, weight = 1.0, doUpdate = True, rawColorVariation = None):
    889         assetTypeID = None
    890         if itemType is None:
    891             return
    892         self.PopulateModifierLocationDicts()
    893         genderID = self.characterMetadata[charID].genderID
    894         if type(itemType) is not tuple:
    895             charGender = ccUtil.GenderIDToPaperDollGender(genderID)
    896             itemTypeData = self.factory.GetItemType(itemType, gender=charGender)
    897             if itemTypeData is None:
    898                 itemTypeLower = itemType.lower()
    899                 if BODY_CATEGORIES.TOPINNER in itemTypeLower:
    900                     self.ApplyItemToDoll(charID, BODY_CATEGORIES.TOPINNER, None, removeFirst=True, doUpdate=False)
    901                 elif BODY_CATEGORIES.BOTTOMINNER in itemTypeLower:
    902                     self.ApplyItemToDoll(charID, BODY_CATEGORIES.BOTTOMINNER, None, removeFirst=True, doUpdate=False)
    903                 else:
    904                     log.LogError("Item type file is missing, can't be loaded", itemType)
    905                 return
    906             assetID, assetTypeID = self.GetAssetAndTypeIDsFromPath(charGender, itemType)
    907             itemType = (assetID, itemTypeData[:3], assetTypeID)
    908         doll = self.characters[charID].doll
    909         category = self.GetCategoryFromResPath(itemType[1][0])
    910         modifierLocationKey = self.modifierLocationsByName.get(category, None)
    911         toRemove = []
    912         for otherCategory, otherResourceID in self.characterMetadata[charID].types.iteritems():
    913             if otherResourceID is None:
    914                 continue
    915             otherResource = cfg.paperdollResources.Get(otherResourceID)
    916             if otherResource.typeID is None:
    917                 continue
    918             if otherResource.clothingRemovesCategory == modifierLocationKey or otherResource.clothingRemovesCategory2 == modifierLocationKey:
    919                 toRemove.append(otherCategory)
    920 
    921         for itemToRemove in toRemove:
    922             self.ApplyItemToDoll(charID, itemToRemove, None, removeFirst=True, doUpdate=False)
    923 
    924         activeMod = self.GetModifierByCategory(charID, category)
    925         if activeMod:
    926             doll.RemoveResource(activeMod.GetResPath(), self.factory)
    927         modifier = doll.AddItemType(self.factory, itemType[1], weight, rawColorVariation)
    928         self.characterMetadata[charID].types[category] = itemType[0]
    929         myTypeID = itemType[2]
    930         if myTypeID:
    931             resource = cfg.paperdollResources.Get(itemType[0])
    932             removesCategory = resource.clothingRemovesCategory
    933             removesCategory2 = resource.clothingRemovesCategory2
    934             if removesCategory is not None and removesCategory != 0 and int(removesCategory) in self.modifierLocationsByKey:
    935                 modifierLocationName = self.modifierLocationsByKey[int(removesCategory)]
    936                 self.ApplyItemToDoll(charID, modifierLocationName, None, removeFirst=True, doUpdate=False)
    937             if removesCategory2 is not None and removesCategory2 != 0 and int(removesCategory2) in self.modifierLocationsByKey:
    938                 modifierLocationName = self.modifierLocationsByKey[int(removesCategory2)]
    939                 self.ApplyItemToDoll(charID, modifierLocationName, None, removeFirst=True, doUpdate=False)
    940         if ccUtil.HasUserDefinedWeight(category):
    941             self.characterMetadata[charID].typeWeights[category] = weight
    942         if category in (ccConst.hair, ccConst.beard, ccConst.eyebrows) and assetTypeID is None:
    943             self.SynchronizeHairColors(charID)
    944         if category == ccConst.lipstick and ccConst.augm_face in self.modifierLocationsByName:
    945             doll.buildDataManager.SetAllAsDirty()
    946         if doUpdate:
    947             self.UpdateDoll(charID, fromWhere='ApplyTypeToDoll')
    948         return modifier
    949 
    950     @telemetry.ZONE_METHOD
    951     def ApplyItemToDoll(self, charID, category, name, removeFirst = False, variation = None, doUpdate = True):
    952         doll = self.characters[charID].doll
    953         modifier = None
    954         modifierFoundForVariationSwitch = False
    955         if name and variation:
    956             modifier = self.GetModifierByCategory(charID, category)
    957             if modifier and modifier.name.split(SEPERATOR_CHAR)[-1] == name:
    958                 modifier.SetVariation(variation)
    959                 modifierFoundForVariationSwitch = True
    960         if not modifierFoundForVariationSwitch:
    961             if removeFirst:
    962                 if name:
    963                     modifier = self.GetModifierByCategory(charID, category)
    964                     if modifier:
    965                         self.RemoveFromCharacterMetadata(charID, category)
    966                         doll.RemoveResource(modifier.respath, self.factory)
    967                 else:
    968                     modifiers = self.GetModifierByCategory(charID, category, getAll=True)
    969                     if modifiers:
    970                         self.RemoveFromCharacterMetadata(charID, category)
    971                         for modifier in modifiers:
    972                             doll.RemoveResource(modifier.respath, self.factory)
    973 
    974             if name:
    975                 modifier = doll.AddResource(category + '/' + name, 1.0, self.factory, variation=variation)
    976             elif not removeFirst:
    977                 modifier = self.GetModifierByCategory(charID, category)
    978                 if modifier:
    979                     self.RemoveFromCharacterMetadata(charID, category)
    980                     doll.RemoveResource(modifier.GetResPath(), self.factory)
    981         if doUpdate:
    982             self.UpdateDoll(charID, fromWhere='ApplyItemToDoll')
    983         return modifier
    984 
    985     @telemetry.ZONE_METHOD
    986     def SetColorValueByCategory(self, charID, category, colorVar1, colorVar2, doUpdate = True):
    987         if colorVar1 is None:
    988             log.LogError('SetColorValue - No Color variation passed in!')
    989             return
    990         color1Value, color1Name, color2Name, variation = self.GetColorsToUse(colorVar1, colorVar2)
    991         modifier = self.GetModifierByCategory(charID, category)
    992         if not modifier:
    993             return
    994         if color1Value:
    995             self.characterMetadata[charID].typeColors[category] = (color1Name, None)
    996             modifier.SetColorizeData(color1Value)
    997         elif colorVar2 is not None:
    998             self.characterMetadata[charID].typeColors[category] = (color1Name, color2Name)
    999             modifier.SetColorVariationDirectly(variation)
   1000         else:
   1001             self.characterMetadata[charID].typeColors[category] = (color1Name, None)
   1002             modifier.SetColorVariationDirectly(variation)
   1003         if category == ccConst.hair:
   1004             self.SynchronizeHairColors(charID)
   1005         if doUpdate:
   1006             self.UpdateDoll(charID, fromWhere='SetColorValueByCategory')
   1007 
   1008     def GetColorsToUse(self, colorVar1, colorVar2, *args):
   1009         if colorVar1 is None:
   1010             log.LogError('GetColorsToUse - No Color variation passed in!')
   1011             return (None, None, None, None)
   1012         if len(colorVar1) == 3:
   1013             colorVar1 = (colorVar1[0], colorVar1[2])
   1014         if colorVar2 is not None and len(colorVar2) == 3:
   1015             colorVar2 = (colorVar2[0], colorVar2[2])
   1016         color1Name, color1Value = colorVar1
   1017         if type(color1Value) == types.TupleType:
   1018             return (color1Value,
   1019              color1Name,
   1020              None,
   1021              None)
   1022         elif colorVar2 is not None:
   1023             color2Name, color2Value = colorVar2
   1024             variation = {}
   1025             if color1Value:
   1026                 if color2Value and 'colors' in color1Value:
   1027                     variation['colors'] = [color1Value['colors'][0], color2Value['colors'][1], color2Value['colors'][2]]
   1028                 if 'pattern' in color1Value:
   1029                     variation['pattern'] = color1Value['pattern']
   1030                 if color2Value and 'patternColors' in color1Value:
   1031                     variation['patternColors'] = [color1Value['patternColors'][0], color2Value['patternColors'][1], color2Value['patternColors'][2]]
   1032                 if 'patternColors' in color1Value:
   1033                     variation['patternColors'] = color1Value['patternColors']
   1034                 if color2Value and 'specularColors' in color1Value:
   1035                     variation['specularColors'] = [color1Value['specularColors'][0], color2Value['specularColors'][1], color2Value['specularColors'][2]]
   1036             return (None,
   1037              color1Name,
   1038              color2Name,
   1039              variation)
   1040         else:
   1041             return (None,
   1042              color1Name,
   1043              None,
   1044              color1Value)
   1045 
   1046     @telemetry.ZONE_METHOD
   1047     def SetColorSpecularityByCategory(self, charID, category, specularity, doUpdate = True):
   1048         modifier = self.GetModifierByCategory(charID, category)
   1049         if modifier:
   1050             self.characterMetadata[charID].typeSpecularity[category] = specularity
   1051             m1 = 1.0 + 0.4 * specularity
   1052             s1 = 0.5 + 0.3 * specularity
   1053             s2 = 0.5 - 0.2 * specularity
   1054             modifier.SetColorVariationSpecularity([(s1,
   1055               s1,
   1056               s1,
   1057               m1), (s2,
   1058               s2,
   1059               s2,
   1060               1.0), (0.5, 0.5, 0.5, 1.0)])
   1061             if doUpdate:
   1062                 self.UpdateDoll(charID, fromWhere='SetColorSpecularityByCategory')
   1063 
   1064     def GetColorSpecularityByCategory(self, charID, category):
   1065         return self.characterMetadata[charID].typeSpecularity.get(category, 0.5)
   1066 
   1067     @telemetry.ZONE_METHOD
   1068     def SetWeightByCategory(self, charID, category, weight, doUpdate = True):
   1069         modifier = self.GetModifierByCategory(charID, category)
   1070         if not modifier and charID in self.characters:
   1071             doll = self.characters[charID].doll
   1072             if doll.gender == GENDER.FEMALE:
   1073                 gender = 0
   1074             else:
   1075                 gender = 1
   1076             options = self.GetAvailableItemsByCategory(category, gender, self.characterMetadata[charID].bloodlineID)
   1077             if options:
   1078                 name = options[0]
   1079                 modifier = doll.AddResource(category + '/' + name, 1.0, self.factory)
   1080         if modifier:
   1081             if ccUtil.HasUserDefinedWeight(category):
   1082                 self.characterMetadata[charID].typeWeights[category] = weight
   1083             modifier.weight = weight
   1084             if doUpdate:
   1085                 self.UpdateDoll(charID, 'SetWeightByCategory')
   1086 
   1087     def GetWeightByCategory(self, charID, category):
   1088         weight = self.characterMetadata[charID].typeWeights.get(category, 0)
   1089         sliderWeight = self.GetSliderWeight(charID, category, weight)
   1090         return sliderWeight
   1091 
   1092     def GetTrueWeight(self, charID, category, sliderWeight):
   1093         weight = sliderWeight
   1094         if category in ccConst.weightLimits:
   1095             limits = ccConst.weightLimits[category]
   1096             color = self.characterMetadata[charID].typeColors.get(category, None)
   1097             minMax = limits.get(color, None) or limits.get('default', None)
   1098             if minMax:
   1099                 diff = minMax[1] - minMax[0]
   1100                 weight = minMax[0] + diff * weight
   1101         return weight
   1102 
   1103     def GetSliderWeight(self, charID, category, trueWeight):
   1104         weight = trueWeight
   1105         if category in ccConst.weightLimits:
   1106             limits = ccConst.weightLimits[category]
   1107             color = self.characterMetadata[charID].typeColors.get(category, None)
   1108             minMax = limits.get(color, None) or limits.get('default', None)
   1109             diff = minMax[1] - minMax[0]
   1110             weight = (weight - minMax[0]) / diff
   1111         return weight
   1112 
   1113     @telemetry.ZONE_METHOD
   1114     def SculptCallBack(self, zone, isFront):
   1115         uicore.layer.charactercreation.controller.ChangeSculptCursor(zone, isFront)
   1116 
   1117     @telemetry.ZONE_METHOD
   1118     def StartEditMode(self, charID, scene, camera, mode = 'sculpt', showPreview = False, callback = None, pickCallback = None, inactiveZones = [], resetAll = False, skipPickSceneReset = False, useThread = 1):
   1119         if useThread:
   1120             if not self.sculpting or resetAll:
   1121                 if not camera:
   1122                     raise ValueError('Cannot start paperdoll sculpting edit mode without a camera.')
   1123             uthread.new(self.StartEditMode_t, *(charID,
   1124              scene,
   1125              camera,
   1126              mode,
   1127              showPreview,
   1128              callback,
   1129              pickCallback,
   1130              inactiveZones,
   1131              resetAll,
   1132              skipPickSceneReset))
   1133         else:
   1134             character = self.characters.get(charID, None)
   1135             if character is None or character.doll.IsBusyUpdating():
   1136                 return
   1137             self.StartEditMode_t(charID, scene, camera, mode, showPreview, callback, pickCallback, inactiveZones, resetAll, skipPickSceneReset)
   1138 
   1139     @telemetry.ZONE_METHOD
   1140     def StartEditMode_t(self, charID, scene, camera, mode, showPreview, callback, pickCallback, inactiveZones, resetAll, skipPickSceneReset):
   1141         character = self.characters.get(charID, None)
   1142         count = 0
   1143         while (character is None or character.doll.IsBusyUpdating()) and count < 100:
   1144             count += 1
   1145             blue.synchro.Yield()
   1146 
   1147         if character is None:
   1148             return
   1149         if not self.sculpting or resetAll:
   1150             if self.sculpting and self.sculpting.highlightGhost:
   1151                 if self.sculpting.highlightGhost.renderStepSlot and self.sculpting.highlightGhost.renderStepSlot.object:
   1152                     self.sculpting.highlightGhost.renderStepSlot.object.job = None
   1153             self.sculpting = PaperDollSculpting(character.avatar, character.doll, scene, camera, self.factory, mode=mode, callback=callback, pickCallback=pickCallback, inactiveZones=inactiveZones)
   1154         else:
   1155             self.sculpting.Reset(character.doll, character.avatar, camera=camera, mode=mode, callback=callback, pickCallback=pickCallback, inactiveZones=inactiveZones, skipPickSceneReset=skipPickSceneReset)
   1156         self.sculptingActive = True
   1157         gender = character.doll.gender
   1158         isMale = gender == 'male'
   1159         if showPreview:
   1160             self.sculpting.RunHighlightPreview(isMale)
   1161 
   1162     @telemetry.ZONE_METHOD
   1163     def StopEditing(self, *args, **kwds):
   1164         if self.sculpting is not None:
   1165             self.sculpting.Stop()
   1166         self.sculptingActive = False
   1167 
   1168     @telemetry.ZONE_METHOD
   1169     def IsSculptingReady(self):
   1170         if not self.sculpting:
   1171             return False
   1172         return self.sculpting.IsReady()
   1173 
   1174     @telemetry.ZONE_METHOD
   1175     def StartPosing(self, charID, callback = None):
   1176         if charID not in self.characters:
   1177             return
   1178         avatar = self.characters[charID].avatar
   1179         network = animparams.GetParamsPerAvatar(avatar, charID)
   1180         try:
   1181             controller = uicore.layer.charactercreation.controller
   1182         except AttributeError:
   1183             return
   1184 
   1185         if controller:
   1186             controller.StartEditMode(showPreview=False, mode='animation', callback=callback)
   1187 
   1188     @telemetry.ZONE_METHOD
   1189     def StopPosing(self, charID):
   1190         avatar = self.characters[charID].avatar
   1191         network = animparams.GetParamsPerAvatar(avatar, charID)
   1192 
   1193     @telemetry.ZONE_METHOD
   1194     def ChangePose(self, v, charID, *args):
   1195         avatar = self.characters[charID].avatar
   1196         network = animparams.GetParamsPerAvatar(avatar, charID)
   1197         if network is not None:
   1198             controlParameter = 'ControlParameters|PortraitPoseNumber'
   1199             network.SetControlParameter(controlParameter, float(v))
   1200 
   1201     @telemetry.ZONE_METHOD
   1202     def SetControlParametersFromList(self, params, charID):
   1203         if charID not in self.characters:
   1204             return
   1205         avatar = self.characters[charID].avatar
   1206         network = animparams.GetParamsPerAvatar(avatar, charID)
   1207         for param in params:
   1208             network.SetControlParameter(param[0], param[1])
   1209 
   1210     @telemetry.ZONE_METHOD
   1211     def ResetFacePose(self, charID):
   1212         if charID not in self.characters:
   1213             return
   1214         self.SetControlParametersFromList(ccConst.FACE_POSE_CONTROLPARAMS, charID)
   1215         genderID = self.characterMetadata[charID].genderID
   1216         avatar = self.characters[charID].avatar
   1217         network = animparams.GetParamsPerAvatar(avatar, charID)
   1218         if genderID == 1:
   1219             network.SetControlParameter('ControlParameters|HeadLookTarget', (0.0, 1.6, 0.5))
   1220         else:
   1221             network.SetControlParameter('ControlParameters|HeadLookTarget', (0.0, 1.5, 0.5))
   1222 
   1223     @telemetry.ZONE_METHOD
   1224     def SetTextureQuality(self, quality):
   1225         self.textureQuality = quality
   1226         resolution = ccConst.TEXTURE_RESOLUTIONS[quality]
   1227         for each in self.characters:
   1228             character = self.characters[each]
   1229             character.doll.textureResolution = resolution
   1230             self.UpdateDollsAvatar(character)
   1231 
   1232     @telemetry.ZONE_METHOD
   1233     def EnableClothSimulation(self, value):
   1234         pass
   1235 
   1236     def MatchDNA(self, character, dna):
   1237         character.doll.MatchDNA(self.factory, dna)
   1238 
   1239     def UpdateDollsAvatar(self, character, *args):
   1240         character.doll.Update(self.factory, character.avatar)
   1241 
   1242     def GetSculpting(self, *args):
   1243         return self.sculpting
   1244 
   1245     def GetSculptingActive(self, *args):
   1246         return self.sculptingActive
   1247 
   1248     @telemetry.ZONE_METHOD
   1249     def RandomizeCharacterSculpting(self, charID, raceID = None, doUpdate = False):
   1250         blue.synchro.Yield()
   1251         if charID in self.characters:
   1252             randomizer = EveDollRandomizer(self.factory)
   1253             randomizer.gender = ccUtil.GenderIDToPaperDollGender(self.characterMetadata[charID].genderID)
   1254             randomizer.bloodline = self.characterMetadata[charID].bloodlineID
   1255             randomizer.race = raceID
   1256             randomizer.SetSculptingLimits()
   1257             blendshapeOptions = randomizer.GetBlendshapeOptions()
   1258             randomizer.ApplyRandomizedResourcesToCharacter(charID, blendshapeOptions)
   1259             if self.sculptingActive:
   1260                 self.sculpting.UpdateFieldsBasedOnExistingValues(self.characters[charID].doll)
   1261             wt = self.GetCharacterWeight(charID)
   1262             self.SetCharacterWeight(charID, wt, doUpdate=False)
   1263             if doUpdate:
   1264                 self.UpdateDoll(charID, fromWhere='RandomizeCharacterSculpting')
   1265         else:
   1266             log.LogError('Cannot randomize character sculpting, character not found: %s' % charID)
   1267 
   1268     @telemetry.ZONE_METHOD
   1269     def RandomizeCharacterGroups(self, charID, raceID, categoryList, doUpdate = False, fullRandomization = False):
   1270         if charID in self.characters:
   1271             doHairDarkness = False
   1272             for category in categoryList:
   1273                 if category in (ccConst.hair, ccConst.beard, ccConst.eyebrows):
   1274                     doHairDarkness = True
   1275                 addWeight = False
   1276                 weightFrom = 0.1
   1277                 if fullRandomization and category.startswith('makeup/'):
   1278                     weightTo = 0.3
   1279                 else:
   1280                     weightTo = 1.0
   1281                 doll = self.characters[charID].doll
   1282                 modifier = self.GetModifierByCategory(charID, category)
   1283                 if modifier:
   1284                     self.RemoveFromCharacterMetadata(charID, category)
   1285                     doll.RemoveResource(modifier.GetResPath(), self.factory)
   1286                 if category in ccConst.addWeightToCategories:
   1287                     addWeight = True
   1288                 oddsDict = {}
   1289                 if doll.gender == 'female':
   1290                     oddsDict = ccConst.femaleOddsOfSelectingNone.copy()
   1291                     if fullRandomization:
   1292                         oddsDict.update(ccConst.femaleOddsOfSelectingNoneFullRandomize)
   1293                 else:
   1294                     oddsDict = ccConst.maleOddsOfSelectingNone.copy()
   1295                     if fullRandomization:
   1296                         oddsDict.update(ccConst.maleOddsOfSelectingNoneFullRandomize)
   1297                 oddsOfSelectingNone = oddsDict.get(category, None)
   1298                 self.RandomizeDollCategory(charID, raceID, category, oddsOfSelectingNone, addWeight, weightFrom, weightTo, fullRandomization)
   1299 
   1300             if doHairDarkness:
   1301                 hairDarkness = round(random.random(), 2)
   1302                 self.SetHairDarkness(charID, hairDarkness)
   1303             if doUpdate:
   1304                 self.UpdateDoll(charID, fromWhere='RandomizeCharacterGroups')
   1305 
   1306     def RandomizeDollCategory(self, charID, raceID, category, oddsOfSelectingNone, addWeight = None, weightFrom = 0, weightTo = 1.0, fullRandomization = False):
   1307         blue.synchro.Yield()
   1308         randomizer = EveDollRandomizer(self.factory)
   1309         randomizer.gender = self.characters[charID].doll.gender
   1310         randomizer.bloodline = self.characterMetadata[charID].bloodlineID
   1311         randomizer.race = raceID
   1312         randomizer.fullRandomization = fullRandomization
   1313         randomizer.AddCategoryForWhitelistRandomization(category, oddsOfSelectingNone)
   1314         if addWeight:
   1315             randomizer.AddPathForWeightRandomization(category, weightFrom, weightTo)
   1316         options = randomizer.GetResources()
   1317         randomizer.ApplyRandomizedResourcesToCharacter(charID, options)
   1318 
   1319     def SetInitialSkintype(self, charID, genderID, bloodlineID, raceID):
   1320         allTypes = sm.GetService('character').GetAvailableTypesByCategory(ccConst.skintype, genderID, raceID)
   1321         defaultColorName = ccConst.DEFAULSKINCOLORFORBLOODLINE.get(bloodlineID)
   1322         skintype_for_bloodline = None
   1323         for skintype in ccConst.SKINTYPE_TO_BLOODLINE:
   1324             if ccConst.SKINTYPE_TO_BLOODLINE[skintype] == bloodlineID:
   1325                 skintype_for_bloodline = skintype
   1326 
   1327         if defaultColorName is None:
   1328             return
   1329         for eachType in allTypes:
   1330             assetID, typeInfo, typeID = eachType
   1331             if typeInfo[2] == defaultColorName and typeInfo[0] == skintype_for_bloodline:
   1332                 self.ApplyTypeToDoll(charID, eachType, doUpdate=False)
   1333                 return eachType
   1334 
   1335     @telemetry.ZONE_METHOD
   1336     def GetPoseData(self):
   1337         if self.sculpting is None:
   1338             return
   1339         self.sculpting.UpdateAnimation([])
   1340         poseDataDict = self.sculpting.animationController.GetAllControlParameterValuesByName(True)
   1341         for k in poseDataDict.keys():
   1342             if k not in paperDollUtil.FACIAL_POSE_PARAMETERS.__dict__:
   1343                 del poseDataDict[k]
   1344 
   1345         return poseDataDict
   1346 
   1347     @telemetry.ZONE_METHOD
   1348     def ValidateDollCustomizationComplete(self, charID):
   1349         genderString = self.characters[charID].doll.gender
   1350         for category, assetID in self.characterMetadata[charID].types.iteritems():
   1351             if assetID is None:
   1352                 self.LogError('Invalid asset chosen for category ', category, '. Please make sure the asset has an associated ID.')
   1353 
   1354         dolData = self.GetCharacterAppearanceInfo(charID)
   1355         return paperDollUtil.HasRequiredClothing(genderString, dolData.modifiers)
   1356 
   1357     def IsSingleCharacterLoaded(self, charID):
   1358         return self.characters.get(charID) is not None
   1359 
   1360     def GetSingleCharactersDoll(self, charID):
   1361         return self.characters[charID].doll
   1362 
   1363     def GetSingleCharactersAvatar(self, charID):
   1364         avatar = None
   1365         try:
   1366             avatar = self.characters[charID].avatar
   1367         except KeyError:
   1368             pass
   1369 
   1370         return avatar
   1371 
   1372     def GetSingleCharacter(self, charID, *args):
   1373         return self.characters[charID]
   1374 
   1375     def GetSingleCharactersMetadata(self, charID, *args):
   1376         return self.characterMetadata[charID]
   1377 
   1378     def GetTypeColors(self, charID, cat):
   1379         return self.characterMetadata[charID].typeColors.get(cat, (None, None))
   1380 
   1381     def GetAssetAndTypeIDsFromPath(self, gender, assetPath):
   1382         if self.assetsToIDs[gender] is None:
   1383             self.assetsToIDs[gender] = {}
   1384             for row in cfg.paperdollResources:
   1385                 if row.resGender == (gender == GENDER.MALE):
   1386                     self.assetsToIDs[gender][self.GetRelativePath(row.resPath).lower()] = (row.paperdollResourceID, row.typeID)
   1387 
   1388         assetPath = self.GetRelativePath(assetPath).lower()
   1389         if assetPath in self.assetsToIDs[gender]:
   1390             return self.assetsToIDs[gender][assetPath]
   1391         else:
   1392             hasContentRole = session.role & ROLE_CONTENT
   1393             if hasContentRole:
   1394                 self.LogWarn('Asset ID', assetPath, 'does not have an associated ID!!')
   1395             return (None, None)
   1396 
   1397     def GetCharacterAppearanceInfo(self, charID):
   1398         dollDNA = self.characters[charID].doll.GetDNA()
   1399         return ConvertDNAForDB(dollDNA, self.characterMetadata[charID])
   1400 
   1401     def CopyCharacterAppearanceInfoForBloodline(self, charID):
   1402         appearanceInfo = self.GetCharacterAppearanceInfo(charID)
   1403         appearanceInfo.sculpts = []
   1404         for i in range(0, len(appearanceInfo.modifiers)):
   1405             modifier = appearanceInfo.modifiers[i]
   1406             if modifier.paperdollResourceVariation is None:
   1407                 newVariation = modifier.paperdollResourceVariation if modifier.paperdollResourceVariation is not None else 0
   1408                 appearanceInfo.modifiers[i] = ModifierRow(modifier.modifierLocationID, modifier.paperdollResourceID, newVariation)
   1409 
   1410         return appearanceInfo
   1411 
   1412     @telemetry.ZONE_METHOD
   1413     def GetDNAFromDBRowsForEntity(self, entityID, dollDNA, gender, bloodline):
   1414         self.ApplyDBRowToDoll(entityID, gender, bloodline, None, dollDNA)
   1415         doll = self.GetSingleCharactersDoll(entityID)
   1416         dna = doll.GetDNA()
   1417         self.RemoveFromCharacterDicts(entityID, fromCharacterDict=True, fromMetadataDict=True)
   1418         return dna
   1419 
   1420     def ConvertWeightKeyValue(self, weight, prefix, posName, negName):
   1421         if prefix in ('thin', 'fat', 'muscular'):
   1422             fmt = '%(prefix)sshape'
   1423         else:
   1424             fmt = '%(prefix)s_%(dir)sshape'
   1425         if weight < 0.0:
   1426             key = fmt % {'prefix': prefix,
   1427              'dir': negName}
   1428             return (key, abs(weight))
   1429         if weight > 0.0:
   1430             key = fmt % {'prefix': prefix,
   1431              'dir': posName}
   1432             return (key, abs(weight))
   1433         return (None, None)
   1434 
   1435     def CachePortraitInfo(self, charID, info):
   1436         self.cachedPortraitInfo[charID] = info
   1437 
   1438     def GetCachedPortraitInfo(self, charID):
   1439         return self.cachedPortraitInfo.get(charID, None)
   1440 
   1441     def EnsureUnderwear(self, charID, genderID, raceID):
   1442         doll = self.GetSingleCharactersDoll(charID)
   1443         bottomUnderwearCount = len(doll.buildDataManager.GetModifiersByCategory(BODY_CATEGORIES.BOTTOMUNDERWEAR))
   1444         if bottomUnderwearCount == 0:
   1445             bottomUnderwearTypes = self.GetAvailableTypesByCategory(ccConst.bottomunderwear, genderID, raceID)
   1446             if len(bottomUnderwearTypes) > 0:
   1447                 self.ApplyTypeToDoll(charID, bottomUnderwearTypes[0], doUpdate=False)
   1448         if doll.gender == GENDER.FEMALE or self.IsChinaServer():
   1449             topUnderwearCount = len(doll.buildDataManager.GetModifiersByCategory(BODY_CATEGORIES.TOPUNDERWEAR))
   1450             if topUnderwearCount == 0:
   1451                 topUnderwearTypes = self.GetAvailableTypesByCategory(ccConst.topunderwear, genderID, raceID)
   1452                 if len(topUnderwearTypes) > 0:
   1453                     self.ApplyTypeToDoll(charID, topUnderwearTypes[0], doUpdate=False)
   1454 
   1455     def GetRelativePath(self, resPath):
   1456         if resPath.lower().startswith('res:'):
   1457             for chopPart in ['Modular/Female/',
   1458              'Modular/Male/',
   1459              'Female/Paperdoll/',
   1460              'Male/Paperdoll/',
   1461              'modular/female/',
   1462              'modular/male/',
   1463              'female/paperdoll/',
   1464              'male/paperdoll/']:
   1465                 startPos = resPath.find(chopPart)
   1466                 if startPos != -1:
   1467                     chopTo = len(chopPart) + startPos
   1468                     resPath = resPath[chopTo:]
   1469 
   1470         return resPath
   1471 
   1472     @telemetry.ZONE_METHOD
   1473     def ApplyDBRowToDoll(self, charID, gender, bloodlineID, raceID, dbRow):
   1474         if charID not in self.characters:
   1475             randomDoll = paperDollUtil.CreateRandomDollNoClothes(gender, bloodlineID, raceID, noRandomize=True)
   1476             self.characters[charID] = utillib.KeyVal(doll=randomDoll, avatar=None)
   1477             self.characterMetadata[charID] = self.GetNewCharacterMetadata(ccUtil.PaperDollGenderToGenderID(gender), bloodlineID)
   1478         sculptLocations = cfg.paperdollSculptingLocations
   1479         modifierLocations = cfg.paperdollModifierLocations
   1480         colors = cfg.paperdollColors
   1481         colorNames = cfg.paperdollColorNames
   1482         resources = cfg.paperdollResources
   1483         if dbRow is None:
   1484             self.LogError('Not applying anything to paperdoll, since dbRow is None')
   1485             return
   1486         for sculptRow in dbRow.sculpts:
   1487             sculptInto = sculptLocations.GetIfExists(sculptRow.sculptLocationID)
   1488             if sculptInto is None:
   1489                 self.LogError('Sculpting information for ', sculptRow.sculptLocationID, 'is missing from BSD, skipping sculpting location.')
   1490                 continue
   1491             for colName, posName, negName in WEIGHT_BREAK_DOWNS:
   1492                 k, v = self.ConvertWeightKeyValue(getattr(sculptRow, colName, 0.0), sculptInto.weightKeyPrefix, posName, negName)
   1493                 if k is not None and v is not None:
   1494                     catName = sculptInto.weightKeyCategory.lower()
   1495                     path = SEPERATOR_CHAR.join([catName, k])
   1496                     self.ApplyItemToDoll(charID, catName, k, doUpdate=False)
   1497                     self.SetWeightByCategory(charID, path, v, doUpdate=False)
   1498 
   1499         modifierWeights = {}
   1500         for colorRow in dbRow.colors:
   1501             colorInfo = colors.GetIfExists(colorRow.colorID)
   1502             if colorInfo is None:
   1503                 self.LogError('Color info missing for  ', colorRow.colorID)
   1504                 continue
   1505             if colorInfo.hasWeight:
   1506                 modifierWeights[colorInfo.colorKey] = colorRow.weight
   1507 
   1508         modifierObjects = {}
   1509         for modifierRow in dbRow.modifiers:
   1510             modifierInfo = modifierLocations.GetIfExists(modifierRow.modifierLocationID)
   1511             resourcesInfo = resources.GetIfExists(modifierRow.paperdollResourceID)
   1512             if modifierInfo is None or resourcesInfo is None:
   1513                 self.LogError('Modifier or resource information missing for ', modifierRow.modifierLocationID, modifierRow.paperdollResourceID)
   1514                 continue
   1515             weight = modifierWeights.get(modifierInfo.modifierKey, 1.0)
   1516             if modifierRow.paperdollResourceVariation != 0 and modifierInfo.variationKey != '':
   1517                 self.ApplyItemToDoll(charID, modifierInfo.variationKey, self.tuckingOptions[modifierInfo.variationKey], removeFirst=True, variation='v%d' % modifierRow.paperdollResourceVariation if modifierRow.paperdollResourceVariation else None, doUpdate=False)
   1518             resPath = self.GetRelativePath(resourcesInfo.resPath)
   1519             modifierObjects[modifierInfo.modifierKey] = self.ApplyTypeToDoll(charID, resPath, weight=weight, doUpdate=False)
   1520 
   1521         genderID = ccUtil.PaperDollGenderToGenderID(gender)
   1522         self.EnsureUnderwear(charID, genderID, raceID)
   1523         for colorRow in dbRow.colors:
   1524             colorInfo = colors.GetIfExists(colorRow.colorID)
   1525             if colorInfo is None:
   1526                 self.LogError('No color info for ', colorRow.colorID)
   1527                 continue
   1528             colorNameInfo = colorNames.GetIfExists(colorRow.colorNameA)
   1529             if colorNameInfo is None:
   1530                 self.LogError('colorA index not in BSD data ', colorRow.colorNameA)
   1531                 continue
   1532             colorNameA = colorNameInfo.colorName
   1533             colorNameBC = None
   1534             if colorRow.colorNameBC != 0:
   1535                 colorNameInfo = colorNames.GetIfExists(colorRow.colorNameBC)
   1536                 if colorNameInfo is not None:
   1537                     colorNameBC = colorNameInfo.colorName
   1538                 else:
   1539                     self.LogError('colorBC index not in BSD data ', colorRow.colorNameBC)
   1540             if colorInfo.colorKey == ccConst.skintone:
   1541                 mod = self.ApplyItemToDoll(charID, ccConst.skintone, 'basic', doUpdate=False)
   1542                 skinColor = (colorNameA, mod.colorVariations[colorNameA])
   1543                 self.SetColorValueByCategory(charID, ccConst.skintone, skinColor, None, doUpdate=False)
   1544             else:
   1545                 if colorInfo.colorKey not in modifierObjects:
   1546                     self.LogError('%s not in modifierObjects' % colorInfo.colorKey)
   1547                     continue
   1548                 mod = modifierObjects[colorInfo.colorKey]
   1549                 if colorNameA not in mod.colorVariations:
   1550                     colorVarA = ('default', mod.colorVariations.get('default', None))
   1551                 else:
   1552                     colorVarA = (colorNameA, mod.colorVariations[colorNameA])
   1553                 colorVarBC = None
   1554                 if colorInfo.hasSecondary and colorNameBC is not None:
   1555                     colorVarBC = (colorNameBC, mod.colorVariations.get(colorNameBC, None))
   1556                 self.SetColorValueByCategory(charID, colorInfo.colorKey, colorVarA, colorVarBC, doUpdate=False)
   1557                 if colorInfo.hasGloss:
   1558                     self.SetColorSpecularityByCategory(charID, colorInfo.colorKey, colorRow.gloss, doUpdate=False)
   1559             if colorInfo.colorKey == ccConst.hair:
   1560                 self.SetHairDarkness(charID, dbRow.appearance.hairDarkness)
   1561                 self.SynchronizeHairColors(charID)
   1562 
   1563     def RemoveFromCharacterDicts(self, charID, fromCharacterDict = True, fromMetadataDict = True):
   1564         if fromCharacterDict:
   1565             self.characters.pop(charID, None)
   1566         if fromMetadataDict:
   1567             self.characterMetadata.pop(charID, None)
   1568 
   1569     def IsChinaServer(self):
   1570         return boot.region == 'optic'