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'