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

spaceObject.py (37795B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\environment\spaceObject\spaceObject.py
      2 import blue
      3 import carbon.common.script.util.mathCommon as mathCommon
      4 import decometaclass
      5 import evegraphics.settings as gfxsettings
      6 import evegraphics.utils as gfxutils
      7 import eveSpaceObject
      8 import eveSpaceObject.spaceobjaudio as spaceobjaudio
      9 import fsdBuiltData.client.explosionBuckets as fsdExplosionBuckets
     10 import geo2
     11 import locks
     12 import logging
     13 import math
     14 import random
     15 import trinity
     16 import uthread2
     17 from cosmetics.common.ships.skins.static_data.skin_type import ShipSkinType
     18 from destructionEffect import destructionType as destructionEffectType
     19 from destructionEffect.destructioneffectmanager import DestructionEffectManager
     20 from evedungeons.client.data import GetAssetManipulations, GetControllerReadyData
     21 from evegraphics.controllers.clientControllerRegistry import ChangeControllerStateFromSlimItem
     22 from evegraphics.explosions.spaceObjectExplosionManager import SpaceObjectExplosionManager
     23 from evegraphics.graphicEffects.skinChange import ChangeSkin, RemoveAllTemporaryModels
     24 from evetypes import GetGraphicID, GetGroupID
     25 from eve.client.script.environment.sofService import GetSofService
     26 from eve.client.script.environment.spaceObject.cosmeticsManager import CosmeticsManager
     27 from eve.client.script.environment.spaceObject.ExplosionManager import ExplosionManager
     28 from eve.common.lib.appConst import GRIDSIZE, groupMercenaryDen, groupMoon, groupPlanet
     29 from fsdBuiltData.common.graphicIDs import GetControllerVariableOverrides, GetSofHullName
     30 from inventorycommon.const import TYPES_THAT_ALWAYS_FACE_THE_SUN
     31 from signals.signal import Signal
     32 logger = logging.getLogger(__name__)
     33 STRUCTURE_SKINCHANGE_MULTIEFFECT_GRAPHIC_ID = 26134
     34 BALL_REMOVAL_DELAY_TO_EXPLOSION_BUCKETS = {240: [3, 4, 5]}
     35 SERVER_CONTROLLER_EVENT_NAMES_TO_CONTROLLER_VARIABLES = {'Activated': 'IsTriggered'}
     36 
     37 def modify_dna(base_dna, mat1 = None, mat2 = None, mat3 = None, mat4 = None, patternMat1 = None, patternMat2 = None, patternName = None, isStructure = False):
     38     if isStructure:
     39         materials = [None,
     40          mat1,
     41          mat3,
     42          mat2]
     43     else:
     44         materials = [mat1,
     45          mat3,
     46          mat2,
     47          None]
     48     for i, material in enumerate(materials):
     49         if material is None:
     50             materials[i] = 'None'
     51 
     52     materials_string = 'material?' + ';'.join(materials)
     53     base_parts = base_dna.split(':')
     54     if len(base_parts) > 3:
     55         base_parts[3] = materials_string
     56     else:
     57         base_parts.append(materials_string)
     58     if patternMat1 is not None or patternMat2 is not None:
     59         pattern_components = []
     60         if patternName is not None:
     61             pattern_components.append(patternName)
     62         if patternMat1 is not None:
     63             pattern_components.append(patternMat1)
     64         if patternMat2 is not None:
     65             pattern_components.append(patternMat2)
     66         if pattern_components:
     67             base_parts.append('pattern?' + ';'.join(pattern_components))
     68     return ':'.join(base_parts)
     69 
     70 
     71 class SpaceObjectLogAdapter(logging.LoggerAdapter):
     72 
     73     def __init__(self, logger, extra = None, so_id = None):
     74         self.so_id = so_id
     75         super(SpaceObjectLogAdapter, self).__init__(logger, extra)
     76 
     77     def add_so_id(self, msg, *args, **kwargs):
     78         msg, kwargs = self.process(msg, kwargs)
     79         so_id_arg = [self.so_id]
     80         if args:
     81             new_args = list(args)
     82             new_args = so_id_arg + new_args
     83         else:
     84             new_args = so_id_arg
     85         msg = '[%s] ' + msg
     86         return (msg, new_args, kwargs)
     87 
     88     def debug(self, msg, *args, **kwargs):
     89         new_msg, new_args, kwargs = self.add_so_id(msg, *args, **kwargs)
     90         self.logger.debug(new_msg, *new_args, **kwargs)
     91 
     92     def info(self, msg, *args, **kwargs):
     93         new_msg, new_args, kwargs = self.add_so_id(msg, *args, **kwargs)
     94         self.logger.info(new_msg, *new_args, **kwargs)
     95 
     96     def warning(self, msg, *args, **kwargs):
     97         new_msg, new_args, kwargs = self.add_so_id(msg, *args, **kwargs)
     98         self.logger.warning(new_msg, *new_args, **kwargs)
     99 
    100     def error(self, msg, *args, **kwargs):
    101         new_msg, new_args, kwargs = self.add_so_id(msg, *args, **kwargs)
    102         self.logger.error(new_msg, *new_args, **kwargs)
    103 
    104     def critical(self, msg, *args, **kwargs):
    105         new_msg, new_args, kwargs = self.add_so_id(msg, *args, **kwargs)
    106         self.logger.critical(new_msg, *new_args, **kwargs)
    107 
    108     def exception(self, msg, *args, **kwargs):
    109         new_msg, new_args, kwargs = self.add_so_id(msg, *args, **kwargs)
    110         kwargs['exc_info'] = 1
    111         self.logger.exception(new_msg, *new_args, **kwargs)
    112 
    113     def log(self, level, msg, *args, **kwargs):
    114         new_msg, new_args, kwargs = self.add_so_id(msg, *args, **kwargs)
    115         self.logger.log(level, new_msg, *new_args, **kwargs)
    116 
    117 
    118 class SpaceObject(decometaclass.WrapBlueClass('destiny.ClientBall')):
    119     __persistdeco__ = 0
    120     __update_on_reload__ = 1
    121 
    122     def __init__(self):
    123         self.destructionEffectId = destructionEffectType.NONE
    124         self.exploded = False
    125         self.unloaded = getattr(self, '__unloadable__', False)
    126         self.model = None
    127         self.additionalModels = []
    128         self.animationStateObjects = {}
    129         self.released = False
    130         self.wreckID = None
    131         self._audioEntity = None
    132         self.logger = logging.getLogger('spaceObject.' + self.__class__.__name__)
    133         self.logger = SpaceObjectLogAdapter(self.logger, so_id=self.id)
    134         self.modelLoadedEvent = locks.Event()
    135         self.modelLoadSignal = Signal(signalName='modelLoadSignal')
    136         self.explosionModel = None
    137         self.typeID = None
    138         self.explosionBucketID = None
    139         self.spaceMgr = None
    140         self.sm = None
    141         self.spaceObjectFactory = None
    142         self.nextModel = None
    143         self._isWaitingForModel = False
    144         self._modelChangeCallbacks = []
    145         self._skinChangeTasklets = []
    146         self.skinMaterialSetID = None
    147         self._dungeonAssetManipulations = None
    148         self.lockedFromRelease = False
    149         self.globalObjectHandlerThread = None
    150         self.explosionManager = ExplosionManager()
    151 
    152     def GetPositionCurve(self):
    153         return self
    154 
    155     def GetTypeID(self):
    156         if self.typeID is None:
    157             self.typeID = self.typeData.get('typeID', None)
    158         return self.typeID
    159 
    160     def SetServices(self, spaceMgr, serviceMgr):
    161         self.spaceMgr = spaceMgr
    162         self.sm = serviceMgr
    163         self.spaceObjectFactory = GetSofService().spaceObjectFactory
    164 
    165     def Prepare(self):
    166         self.typeID = self.typeData.get('typeID', None)
    167         self.explosionBucketID = fsdExplosionBuckets.GetExplosionBucketIDByTypeID(self.typeID)
    168         self.LoadModel()
    169         self.Assemble()
    170         try:
    171             self.SetControllerVariablesFromSlimItem(self.typeData.get('slimItem', None))
    172             self.SetControllerVariablesFromGraphicID()
    173             self.SetDungeonAssetManipulations()
    174             self.model.StartControllers()
    175         except AttributeError:
    176             pass
    177 
    178     def SetControllerVariablesFromGraphicID(self):
    179         gid = GetGraphicID(self.typeID)
    180         overrides = GetControllerVariableOverrides(gid)
    181         if overrides is not None:
    182             for name, value in overrides.iteritems():
    183                 self.SetControllerVariable(name, value)
    184 
    185     def SetControllerVariablesFromSlimItem(self, slimItem):
    186         if slimItem is None:
    187             return
    188         ChangeControllerStateFromSlimItem(self.GetTypeID(), self, slimItem)
    189 
    190     def SetControllerVariableFromEvent(self, eventName, value, delay = None):
    191         if delay:
    192             blue.pyos.synchro.SleepSim(delay)
    193         controllerVariableName = SERVER_CONTROLLER_EVENT_NAMES_TO_CONTROLLER_VARIABLES.get(eventName)
    194         if controllerVariableName is None:
    195             try:
    196                 self.model.HandleControllerEvent(eventName)
    197             except AttributeError:
    198                 pass
    199 
    200         else:
    201             self.SetControllerVariable(controllerVariableName, value)
    202 
    203     def LoadDungeonAssetManipulations(self):
    204         slimItem = self.typeData.get('slimItem')
    205         if slimItem.dunID and slimItem.dunObjectID:
    206             return GetAssetManipulations(slimItem.dunID, slimItem.dunObjectID)
    207 
    208     def SetDungeonAssetManipulations(self):
    209         if self._dungeonAssetManipulations is None:
    210             self._dungeonAssetManipulations = self.LoadDungeonAssetManipulations()
    211             if self._dungeonAssetManipulations is None:
    212                 return
    213         for name, value in self._dungeonAssetManipulations.iteritems():
    214             value = GetControllerReadyData(value)
    215             if isinstance(value, float):
    216                 self.SetControllerVariable(name, value)
    217 
    218         for externalParameter in blue.FindInterface(self.model, 'Tr2ExternalParameter'):
    219             if externalParameter.name in self._dungeonAssetManipulations:
    220                 try:
    221                     externalParameter.SetValue(self._dungeonAssetManipulations[externalParameter.name])
    222                 except TypeError:
    223                     pass
    224 
    225     def UnblockModelEvent(self):
    226         self.modelLoadedEvent.set()
    227 
    228     def _GetComponentRegistry(self):
    229         return self.ballpark.componentRegistry
    230 
    231     def SetControllerVariable(self, name, value):
    232         self.logger.debug('SpaceObject: set controller variable %s to %s', name, value)
    233         try:
    234             self.model.SetControllerVariable(name, float(value))
    235         except AttributeError:
    236             pass
    237 
    238         for state, item in self.animationStateObjects.items():
    239             try:
    240                 item.SetControllerVariable(name, float(value))
    241             except AttributeError:
    242                 pass
    243 
    244     def TriggerStateObject(self, state):
    245         if state in self.animationStateObjects:
    246             self.RemoveAndClearModel(self.animationStateObjects[state])
    247             del self.animationStateObjects[state]
    248         if state in self.typeData['animationStateObjects']:
    249             dnaToLoad = self.typeData['animationStateObjects'][state]
    250             stateObject = self.spaceObjectFactory.BuildFromDNA(dnaToLoad)
    251             self._SetupModelAttributes(stateObject, '%d_%s' % (self.id, state))
    252             if self.model is not None:
    253                 stateObject.rotationCurve = self.model.rotationCurve
    254                 stateObject.StartControllers()
    255                 self._AddModelToScene(stateObject)
    256                 self.animationStateObjects[state] = stateObject
    257 
    258     def GetModel(self):
    259         if not self.model:
    260             try:
    261                 self.modelLoadedEvent.wait()
    262             except RuntimeError:
    263                 return None
    264 
    265         return self.model
    266 
    267     def GetSkinState(self):
    268         characterID = self.typeData['slimItem'].charID
    269         itemID = self.typeData['slimItem'].itemID
    270         skinState = sm.GetService('cosmeticsSvc').GetCachedAppliedSkinState(characterID, itemID)
    271         return skinState
    272 
    273     def GetDNA(self):
    274         if self.skinMaterialSetID is None:
    275             skinState = self.GetSkinState()
    276             if skinState is None:
    277                 materialSetID = self.typeData.get('slimItem').skinMaterialSetID
    278                 if materialSetID is not None:
    279                     self.skinMaterialSetID = materialSetID
    280                     self.logger.info('SKIN STATES - Apply SKIN to NPC, material set: %s', materialSetID)
    281             else:
    282                 skin_type = skinState.skin_type
    283                 if skin_type == ShipSkinType.THIRD_PARTY_SKIN:
    284                     return CosmeticsManager.CreateSOFDNAfromSkinState(skinState, self.GetTypeID())
    285                 if skin_type == ShipSkinType.FIRST_PARTY_SKIN:
    286                     skinData = skinState.skin_data
    287                     if skinData is not None:
    288                         self.skinMaterialSetID = sm.GetService('cosmeticsSvc').GetFirstPartySkinMaterialSetID(skinID=skinData.skin_id)
    289                 elif skin_type == ShipSkinType.NO_SKIN:
    290                     pass
    291         return self._BuildSOFDNAFromTypeID()
    292 
    293     def _BuildSOFDNAFromTypeID(self):
    294         return gfxutils.BuildSOFDNAFromTypeID(typeID=self.GetTypeID(), materialSetID=self.skinMaterialSetID)
    295 
    296     def _RetryLoadModelResource(self, fileName, sofDna, remainingRetryAttempts):
    297         if self.released:
    298             return
    299         trinity.WaitForResourceLoads()
    300         blue.synchro.Yield()
    301         model = self._LoadModelResource(fileName, sofDna, retryOnFail=False)
    302         if model is not None:
    303             self._isWaitingForModel = False
    304             self._ReplaceDummyModel(model)
    305             return
    306         if remainingRetryAttempts > 1:
    307             remainingRetryAttempts -= 1
    308             uthread2.call_after_wallclocktime_delay(self._RetryLoadModelResource, 1.0, fileName, sofDna, remainingRetryAttempts)
    309             return
    310         self._isWaitingForModel = False
    311         graphic_id = self.typeData['graphicID']
    312         type_id = str(self.typeData['typeID'])
    313         sof_hull_name = str(GetSofHullName(graphic_id))
    314         graphic_id = str(graphic_id)
    315         logger.error('[%s] Error: Object type %s has invalid graphicFile, using graphicID: %s', str(self.id), type_id, graphic_id, extra={'sentry_tags': {'type_id': type_id,
    316                          'graphic_id': graphic_id,
    317                          'sof_hull_name': sof_hull_name}})
    318 
    319     def _ReplaceDummyModel(self, newModel):
    320         if self.model is None or newModel is None:
    321             return
    322         if not self._isWaitingForModel:
    323             return
    324         self.logger.debug('Replacing dummyModel for type: %s', self.typeData['typeID'])
    325         controllerEvents = self.model.GetControllerVariables()
    326         self.RemoveAndClearModel(self.model)
    327         self._SetupModelAndAddToScene(loadedModel=newModel)
    328         for controllerEvent, value in controllerEvents.items():
    329             newModel.SetControllerVariable(controllerEvent, float(value))
    330 
    331         newModel.StartControllers()
    332 
    333     def _LoadModelResource(self, fileName = None, sofDna = None, retryOnFail = True):
    334         model = None
    335         sofDNA = sofDna or self.GetDNA()
    336         self.logger.debug("LoadModel fileName='%s' sofDNA='%s'", fileName, sofDNA)
    337         if sofDNA is not None and fileName is None:
    338             model = self.spaceObjectFactory.BuildFromDNA(sofDNA)
    339             skinState = self.GetSkinState()
    340             if skinState is not None:
    341                 skin_type = skinState.skin_type
    342                 if skin_type == ShipSkinType.THIRD_PARTY_SKIN:
    343                     CosmeticsManager.UpdatePatternProjectionParametersFromSkinState(skinState, model, self.GetTypeID())
    344                     blend_mode = skinState.skin_data.slot_layout.pattern_blend_mode
    345                     if blend_mode is not None:
    346                         CosmeticsManager.SetBlendMode(model, blendMode=blend_mode)
    347         else:
    348             if fileName is None:
    349                 fileName = self.typeData.get('graphicFile')
    350             if fileName is not None and len(fileName):
    351                 model = blue.resMan.LoadObject(fileName)
    352         if model is None:
    353             graphicID = self.typeData['graphicID']
    354             typeID = str(self.typeData['typeID'])
    355             if graphicID is None and fileName is None and sofDNA is None:
    356                 logger.error('Type %s is attempting to load a model without a resource defined', typeID, extra={'sentry_tags': {'type_id': typeID}})
    357             elif retryOnFail:
    358                 logger.debug('Type %s failed to load and was assigned a temporary model resource', typeID)
    359                 model = trinity.EveShip2()
    360                 model.name = 'ToBeReplaced_' + typeID
    361                 retryAttempts = 3
    362                 self._isWaitingForModel = True
    363                 uthread2.StartTasklet(self._RetryLoadModelResource, fileName, sofDNA, retryAttempts)
    364         else:
    365             self._isWaitingForModel = False
    366         return model
    367 
    368     def _SetupModelAndAddToScene(self, fileName = None, loadedModel = None, addToScene = True, sofDna = None):
    369         if loadedModel:
    370             model = loadedModel
    371         else:
    372             model = self._LoadModelResource(fileName, sofDna)
    373         if self.released:
    374             return
    375         if not model:
    376             self.logger.error('Could not load model for spaceobject. FileName:%s SofDna:%s typeID:%s', fileName, sofDna, getattr(self, 'typeID', '?'))
    377             return
    378         self._SetupModelAttributes(model, '%d' % self.id)
    379         if addToScene:
    380             if self.spaceMgr.GetScene() is None:
    381                 self.logger.warning('scene no longer exists, skipping adding model to sceneFileName:%s SofDna:%s typeID:%s', fileName, sofDna, getattr(self, 'typeID', '?'))
    382             else:
    383                 self._AddModelToScene(model)
    384         return model
    385 
    386     def _SetupModelAttributes(self, model, objectName):
    387         model.translationCurve = self
    388         model.rotationCurve = self
    389         if not model.name.startswith('ToBeReplaced'):
    390             model.name = objectName
    391         if hasattr(model, 'albedoColor'):
    392             model.albedoColor = eveSpaceObject.GetAlbedoColor(model)
    393 
    394     def _AddModelToScene(self, model):
    395         if model is not None:
    396             scene = self.spaceMgr.GetScene()
    397             if scene is not None:
    398                 objectIsGlobal = False
    399                 if hasattr(model, 'translationCurve'):
    400                     if model.translationCurve is not None and hasattr(model.translationCurve, 'isGlobal'):
    401                         objectIsGlobal = model.translationCurve.isGlobal
    402                 scene.objects.append(model)
    403                 if objectIsGlobal and type(model) == trinity.EveStation2:
    404                     if not self.globalObjectHandlerThread:
    405                         self.model = model
    406                         self.globalObjectHandlerThread = uthread2.StartTasklet(self._GlobalObjectHandlerThread)
    407             else:
    408                 raise RuntimeError('Invalid object loaded by spaceObject: %s' % str(model))
    409 
    410     def IsModelWithinDistance(self, distance):
    411         distanceToObject = 0.0
    412         if self.model is not None:
    413             try:
    414                 distanceToObject = self.model.translationCurve.surfaceDist
    415             except AttributeError:
    416                 distanceToObject = geo2.Vec3Length(self.model.modelWorldPosition)
    417 
    418         return distanceToObject < distance
    419 
    420     def _GlobalObjectHandlerThread(self):
    421         while True:
    422             if self.model is not None:
    423                 try:
    424                     shouldUpdate = self.IsModelWithinDistance(float(GRIDSIZE) * 1000.0)
    425                     self.model.mute = not shouldUpdate
    426                     self.model.display = shouldUpdate
    427                     self.model.update = shouldUpdate
    428                     blue.synchro.SleepSim(1000)
    429                 except AttributeError:
    430                     break
    431 
    432             else:
    433                 blue.synchro.Yield()
    434             if not self.globalObjectHandlerThread:
    435                 break
    436 
    437     def LoadAdditionalModel(self, fileName = None, sofDna = None):
    438         model = self._SetupModelAndAddToScene(fileName=fileName, sofDna=sofDna)
    439         if model is not None:
    440             model.StartControllers()
    441         if fileName is not None:
    442             self.additionalModels.append(model)
    443         return model
    444 
    445     def NotifyModelLoaded(self):
    446         if self.model is not None:
    447             self.logger.debug('SpaceObject - NotifyModelLoaded')
    448             self.modelLoadedEvent.set()
    449             self.modelLoadSignal()
    450             self.sm.GetService('FxSequencer').NotifyModelLoaded(self.id)
    451         else:
    452             self.logger.warning('SpaceObject - NotifyModelLoaded called without a model present, no notification was done')
    453 
    454     def RegisterForModelLoad(self, func):
    455         self.modelLoadSignal.connect(func)
    456 
    457     def LoadModel(self, fileName = None, loadedModel = None, notify = True, addToScene = True):
    458         self.model = self._SetupModelAndAddToScene(fileName, loadedModel, addToScene)
    459         if self.model is None:
    460             return
    461         if GetGroupID(self.GetTypeID()) == groupMercenaryDen:
    462             self.SetStaticRotation()
    463         self.SetupAnimationInformation(self.model)
    464         if notify:
    465             self.NotifyModelLoaded()
    466 
    467     def SetupAnimationInformation(self, model):
    468         if not hasattr(model, 'animationUpdater'):
    469             return
    470         if self._audioEntity is None:
    471             self._audioEntity = self._GetGeneralAudioEntity(model=model)
    472         if model is not None and model.animationUpdater is not None:
    473             model.animationUpdater.eventListener = self._audioEntity
    474 
    475     def Assemble(self):
    476         pass
    477 
    478     def _ShouldFaceTheSun(self):
    479         slimItem = self.typeData.get('slimItem')
    480         return slimItem.typeID in TYPES_THAT_ALWAYS_FACE_THE_SUN
    481 
    482     def _RotateToFaceTheSun(self):
    483         if (self.x, self.y, self.z) == (0.0, 0.0, 0.0):
    484             directionFromSunToMe = (1.0, 0.0, 0.0)
    485         else:
    486             directionFromSunToMe = (self.x, self.y, self.z)
    487         self.AlignToDirection(directionFromSunToMe)
    488 
    489     def SetStaticRotation(self):
    490         if self.model is None:
    491             return
    492         if self._ShouldFaceTheSun():
    493             self._RotateToFaceTheSun()
    494             return
    495         self.model.rotationCurve = None
    496         rot = self.typeData.get('dunRotation', None)
    497         if rot:
    498             yaw, pitch, roll = map(math.radians, rot)
    499             quat = geo2.QuaternionRotationSetYawPitchRoll(yaw, pitch, roll)
    500             if hasattr(self.model, 'rotation'):
    501                 self.model.rotation = quat
    502             else:
    503                 self.model.rotationCurve = trinity.Tr2RotationAdapter()
    504                 self.model.rotationCurve.value = quat
    505                 for stateObject in self.animationStateObjects.values():
    506                     stateObject.rotationCurve = self.model.rotationCurve
    507 
    508     def _FindClosestBallDir(self, constgrp):
    509         bp = self.sm.StartService('michelle').GetBallpark()
    510         dist = 1e+100
    511         closestID = None
    512         for ballID, slimItem in bp.slimItems.iteritems():
    513             if slimItem.groupID == constgrp:
    514                 test = bp.DistanceBetween(self.id, ballID)
    515                 if test < dist:
    516                     dist = test
    517                     closestID = ballID
    518 
    519         if closestID is None:
    520             return (1.0, 0.0, 0.0)
    521         ball = bp.GetBall(closestID)
    522         direction = geo2.Vec3SubtractD((self.x, self.y, self.z), (ball.x, ball.y, ball.z))
    523         return direction
    524 
    525     def FindClosestMoonDir(self):
    526         return self._FindClosestBallDir(groupMoon)
    527 
    528     def FindClosestPlanetDir(self):
    529         return self._FindClosestBallDir(groupPlanet)
    530 
    531     def GetStaticDirection(self):
    532         return self.typeData.get('dunDirection', None)
    533 
    534     def SetStaticDirection(self):
    535         if self.model is None:
    536             return
    537         self.model.rotationCurve = None
    538         direction = self.GetStaticDirection()
    539         if direction is None:
    540             self.logger.warning('No static direction defined - no rotation will be applied')
    541             return
    542         self.AlignToDirection(direction)
    543 
    544     def AlignToDirection(self, direction):
    545         if not self.model:
    546             return
    547         zaxis = direction
    548         if geo2.Vec3LengthSqD(zaxis) > 0.0:
    549             zaxis = geo2.Vec3NormalizeD(zaxis)
    550             xaxis = geo2.Vec3CrossD(zaxis, (0, 1, 0))
    551             if geo2.Vec3LengthSqD(xaxis) == 0.0:
    552                 zaxis = geo2.Vec3AddD(zaxis, mathCommon.RandomVector(0.0001))
    553                 zaxis = geo2.Vec3NormalizeD(zaxis)
    554                 xaxis = geo2.Vec3CrossD(zaxis, (0, 1, 0))
    555             xaxis = geo2.Vec3NormalizeD(xaxis)
    556             yaxis = geo2.Vec3CrossD(xaxis, zaxis)
    557         else:
    558             self.logger.error('Invalid direction (%s). Unable to rotate it.', direction)
    559             return
    560         mat = ((xaxis[0],
    561           xaxis[1],
    562           xaxis[2],
    563           0.0),
    564          (yaxis[0],
    565           yaxis[1],
    566           yaxis[2],
    567           0.0),
    568          (-zaxis[0],
    569           -zaxis[1],
    570           -zaxis[2],
    571           0.0),
    572          (0.0, 0.0, 0.0, 1.0))
    573         quat = geo2.QuaternionRotationMatrix(mat)
    574         if hasattr(self.model, 'modelRotationCurve'):
    575             if not self.model.modelRotationCurve:
    576                 self.model.modelRotationCurve = trinity.Tr2RotationAdapter()
    577             self.model.modelRotationCurve.value = quat
    578         else:
    579             self.model.rotationCurve = None
    580 
    581     def UnSync(self, model = None):
    582         if model is None:
    583             model = self.model
    584         if model is None:
    585             return
    586         scaling = 0.95 + random.random() * 0.1
    587         for adapter in model.Find(['trinity.Tr2RotationAdapter', 'trinity.Tr2TranslationAdapter']):
    588             adapter.RandomizeStart()
    589             adapter.ScaleTime(scaling)
    590 
    591     def Display(self, display = 1, canYield = True):
    592         if self.model is None:
    593             if display:
    594                 self.logger.warning('Display - No model')
    595             return
    596         if canYield:
    597             blue.synchro.Yield()
    598         if session.shipid == self.id and display and self.IsCloaked():
    599             self.sm.StartService('FxSequencer').OnSpecialFX(self.id, None, None, None, None, 'effects.CloakNoAmim', 0, 1, 0, 5, 0)
    600             return
    601         if self.model:
    602             self.model.display = display
    603 
    604     def IsCloaked(self):
    605         return self.isCloaked
    606 
    607     def OnDamageState(self, damageState):
    608         pass
    609 
    610     def _OnDamageState(self, damageState):
    611         if self.model is not None and damageState is not None:
    612             if not hasattr(self.model, 'SetImpactDamageState'):
    613                 return
    614             states = [ (d if d is not None else 0.0) for d in damageState ]
    615             self.model.SetImpactDamageState(states[0], states[1], states[2], False)
    616 
    617     def GetDamageState(self):
    618         bp = sm.GetService('michelle').GetBallpark()
    619         if bp is not None:
    620             return bp.GetDamageState(self.id)
    621 
    622     def _UpdateImpacts(self, model = None):
    623         if model is None:
    624             model = self.model
    625         states = self.GetDamageState()
    626         if states is not None and model is not None:
    627             damageState = [ (d if d is not None else 0.0) for d in states ]
    628             model.SetImpactDamageState(damageState[0], damageState[1], damageState[2], True)
    629 
    630     def DoFinalCleanup(self):
    631         if not self.sm.IsServiceRunning('FxSequencer'):
    632             return
    633         self.sm.GetService('FxSequencer').RemoveAllBallActivations(self.id)
    634         self.globalObjectHandlerThread = None
    635         if destructionEffectType.isExplosionOrOverride(self.destructionEffectId):
    636             self.ClearExplosion()
    637         if not self.released:
    638             self.destructionEffectId = destructionEffectType.NONE
    639             self.Release()
    640             SpaceObjectExplosionManager.AddExplosionTimeNotifications(self.id, 0, 0)
    641         elif self.HasModels():
    642             scene = self.spaceMgr.GetScene()
    643             self.ClearAndRemoveAllModels(scene)
    644 
    645     def ClearExplosion(self, model = None):
    646         if hasattr(self, 'gfx') and self.gfx is not None:
    647             self.RemoveAndClearModel(self.gfx)
    648             self.gfx = None
    649         if self.explosionModel is not None:
    650             if getattr(self, 'explosionDisplayBinding', False):
    651                 self.explosionDisplayBinding.destinationObject = None
    652                 self.explosionDisplayBinding = None
    653             self.RemoveAndClearModel(self.explosionModel)
    654             self.explosionModel = None
    655 
    656     def Release(self, origin = None):
    657         uthread2.StartTasklet(self._Release, origin)
    658 
    659     def _Release(self, origin = None):
    660         if self.released and not self.lockedFromRelease:
    661             return
    662         self.released = True
    663         if self.lockedFromRelease:
    664             return
    665         self._modelChangeCallbacks = []
    666         if destructionEffectType.isExplosionOrOverride(self.destructionEffectId):
    667             delay = self.Explode()
    668             if delay:
    669                 blue.synchro.SleepSim(delay)
    670         elif self.destructionEffectId == destructionEffectType.DISSOLVE:
    671             destructionEffectManager = DestructionEffectManager(self)
    672             destructionEffectManager.PlayDissolveEffect()
    673         if self.destructionEffectId in destructionEffectType.DURATION_BY_DESTRUCTION_TYPE:
    674             delay = destructionEffectType.DURATION_BY_DESTRUCTION_TYPE[self.destructionEffectId]
    675             if delay:
    676                 blue.synchro.SleepSim(delay)
    677         self.Display(display=0, canYield=False)
    678         for model in self.additionalModels:
    679             if model is not None:
    680                 model.display = False
    681 
    682         if hasattr(self.model, 'animationUpdater') and self.model.animationUpdater is not None:
    683             self.model.animationUpdater.eventListener = None
    684         self.ClearAudio()
    685         scene = self.spaceMgr.GetScene()
    686         camera = sm.GetService('sceneManager').GetActiveSpaceCamera()
    687         lookingAt = camera.GetLookAtItemID()
    688         interestID = camera.GetTrackItemID()
    689         if self.destructionEffectId != destructionEffectType.NONE and (self.id == lookingAt or interestID == self.id):
    690             self.RemoveAllModelsFromScene(scene)
    691         else:
    692             self.ClearAndRemoveAllModels(scene)
    693 
    694     def ClearAudio(self):
    695         self._audioEntity = None
    696         if self.model is not None:
    697             if hasattr(self.model, 'observers') and len(self.model.observers) > 0:
    698                 for observer in self.model.observers:
    699                     observer.observer = None
    700 
    701             if hasattr(self.model, 'animationUpdater') and self.model.animationUpdater is not None:
    702                 self.model.animationUpdater.eventListener = None
    703 
    704     def HasModels(self):
    705         return self.model is not None
    706 
    707     def ClearAnimationStateObjects(self, scene = None):
    708         scene = scene or self.spaceMgr.GetScene()
    709         for stateObject in self.animationStateObjects.values():
    710             self.RemoveAndClearModel(stateObject, scene)
    711 
    712         self.animationStateObjects.clear()
    713 
    714     def ClearAndRemoveAllModels(self, scene):
    715         self.RemoveAndClearModel(self.model, scene)
    716         self.model = None
    717         for m in self.additionalModels:
    718             self.RemoveAndClearModel(m, scene)
    719 
    720         self.additionalModels = []
    721         self.ClearAnimationStateObjects()
    722 
    723     def RemoveAllModelsFromScene(self, scene):
    724         if scene is None:
    725             return
    726         scene.objects.fremove(self.model)
    727         for m in self.additionalModels:
    728             scene.objects.fremove(m)
    729 
    730         self.ClearAnimationStateObjects()
    731 
    732     def RemoveAndClearModel(self, model, scene = None):
    733         if model:
    734             self._Clearcurves(model)
    735         else:
    736             self.released = True
    737             return
    738         self.RemoveFromScene(model, scene)
    739 
    740     def _Clearcurves(self, model):
    741         if hasattr(model, 'translationCurve'):
    742             model.translationCurve = None
    743             model.rotationCurve = None
    744 
    745     def RemoveFromScene(self, model, scene):
    746         if scene is None:
    747             scene = self.spaceMgr.GetScene()
    748         if scene:
    749             scene.objects.fremove(model)
    750 
    751     def GetExplosionLookAtDelay(self):
    752         return eveSpaceObject.GetDeathExplosionLookDelay(self.model, self.radius)
    753 
    754     def GetTotalDestructionEffectTime(self):
    755         if destructionEffectType.isExplosionOrOverride(self.destructionEffectId):
    756             for delay, explosionBucketList in BALL_REMOVAL_DELAY_TO_EXPLOSION_BUCKETS.iteritems():
    757                 if self.explosionBucketID in explosionBucketList:
    758                     return delay
    759 
    760         elif self.destructionEffectId in destructionEffectType.DURATION_BY_DESTRUCTION_TYPE:
    761             return destructionEffectType.DURATION_BY_DESTRUCTION_TYPE[self.destructionEffectId]
    762         return 60
    763 
    764     def Explode(self):
    765         if self.exploded:
    766             return 0
    767         slimItem = self.typeData.get('slimItem')
    768         self.sm.ScatterEvent('OnObjectExplode', slimItem.typeID, slimItem.itemID)
    769         self.exploded = True
    770         if gfxsettings.Get(gfxsettings.UI_EXPLOSION_EFFECTS_ENABLED):
    771             if self.explosionBucketID is not None and self.model is not None:
    772                 locator = getattr(self.model, 'lastDamageLocatorHit', -1)
    773                 self.logger.debug("Exploding explosion bucket '%s' at locator %s", self.explosionBucketID, locator)
    774                 scene = self.spaceMgr.GetScene()
    775                 special = self.destructionEffectId == destructionEffectType.EXPLOSION_OVERRIDE
    776                 wreckSwitchTime, _, __ = SpaceObjectExplosionManager.ExplodeBucketForBall(self, scene, locator, special=special)
    777                 return wreckSwitchTime
    778             self.logger.warning('GraphicID %s does not have an explosionBucketID. typeID: %s', self.typeData['graphicID'], self.typeID)
    779         return 0
    780 
    781     def IsTriggeredForDestruction(self):
    782         return self.destructionEffectId is not None and self.destructionEffectId is not destructionEffectType.NONE
    783 
    784     def PrepareForFiring(self):
    785         pass
    786 
    787     def GetEventNameFromSlimItem(self, defaultSoundUrl):
    788         slimItem = self.typeData.get('slimItem')
    789         eventName = spaceobjaudio.GetSoundUrl(slimItem, defaultSoundUrl)
    790         return eventName
    791 
    792     def SetupAmbientAudio(self, defaultSoundUrl = None, model = None):
    793         audioUrl = self.GetEventNameFromSlimItem(defaultSoundUrl)
    794         if audioUrl is None:
    795             return
    796         audentity = self._GetGeneralAudioEntity(model=model)
    797         if audentity is not None:
    798             spaceobjaudio.PlayAmbientAudio(audentity, audioUrl)
    799 
    800     def SetupSharedAmbientAudio(self, defaultSoundUrl = None):
    801         eventName = self.GetEventNameFromSlimItem(defaultSoundUrl)
    802         if eventName is None or self.model is None:
    803             return
    804         spaceobjaudio.SetupSharedEmitterForAudioEvent(self.id, self.model, eventName)
    805 
    806     def LookAtMe(self):
    807         pass
    808 
    809     def _GetGeneralAudioEntity(self, model = None, recreate = False):
    810         if model is None:
    811             model = self.model
    812         if model is None:
    813             self._audioEntity = None
    814             self.logger.warning('model is None, cannot play audio.')
    815         elif recreate or self._audioEntity is None:
    816             self._audioEntity = spaceobjaudio.SetupAudioEntity(model, self.id)
    817         return self._audioEntity
    818 
    819     def PlayGeneralAudioEvent(self, eventName):
    820         audentity = self._GetGeneralAudioEntity()
    821         if audentity is not None:
    822             spaceobjaudio.SendEvent(audentity, eventName)
    823 
    824     def GetNamedAudioEmitterFromObservers(self, emitterName):
    825         if getattr(self, 'model', None) is None:
    826             return
    827         for triObserver in self.model.Find('trinity.TriObserverLocal'):
    828             if triObserver.observer.name.lower() == emitterName:
    829                 return triObserver.observer
    830 
    831     def PlaySound(self, event):
    832         if self.model is None:
    833             return
    834         if hasattr(self.model, 'observers'):
    835             for obs in self.model.observers:
    836                 obs.observer.SendEvent(unicode(event))
    837                 return
    838 
    839         self.logger.error("Space Object: %s can't play sound. Sound observer not found.", self.typeData.get('typeName', None))
    840 
    841     def OnSlimItemUpdated(self, slimItem):
    842         oldSlim = self.typeData['slimItem']
    843         self.typeData['slimItem'] = slimItem
    844         if getattr(oldSlim, 'skinMaterialSetID') != getattr(slimItem, 'skinMaterialSetID'):
    845             self._skinChangeTasklets.append(uthread2.StartTasklet(self.ChangeSkin))
    846         self.SetControllerVariablesFromSlimItem(slimItem)
    847 
    848     def ChangeSkin(self, structure_id = None, paintwork = None):
    849         if self.model is None:
    850             return
    851         if sm.GetService('subway').InJump():
    852             return
    853         oldModel = self.nextModel or self.model
    854         nextModel = self._LoadModelResource(None)
    855         blue.resMan.Wait()
    856         self.fitted = False
    857         self.nextModel = self._SetupModelAndAddToScene(loadedModel=nextModel, addToScene=False)
    858         if self.nextModel is None:
    859             return
    860         self.SetupAnimationInformation(self.nextModel)
    861         self._SetupModelAttributes(self.nextModel, '%d' % self.id)
    862         self.ChangingSkin(self.nextModel)
    863         ChangeSkin(oldModel, self.nextModel, self.spaceMgr.GetScene(), postSkinChangeCallback=self.PostSkinChangeCallback)
    864 
    865     def ChangingSkin(self, nextModel):
    866         uthread2.StartTasklet(self.logoLoader.Load, self.nextModel, self.typeData.get('slimItem'))
    867 
    868     def PostChangingSkin(self):
    869         pass
    870 
    871     def CancelSkinChange(self):
    872         for skinChangeTasklet in self._skinChangeTasklets:
    873             skinChangeTasklet.kill()
    874 
    875         self._skinChangeTasklets = []
    876         if self.model != self.nextModel and self.nextModel is not None:
    877             self.PostSkinChangeCallback(self.model, self.nextModel)
    878         RemoveAllTemporaryModels(self.spaceMgr.GetScene())
    879 
    880     def PostSkinChangeCallback(self, oldModel, newModel):
    881         self.RemoveAndClearModel(oldModel)
    882         self.model = newModel
    883         self.model.clipSphereFactor2 = 0.0
    884         self.PostChangingSkin()
    885         self._GetGeneralAudioEntity(self.model, recreate=True)
    886         self.NotifyModelLoaded()
    887         for callback in self._modelChangeCallbacks:
    888             callback(newModel)
    889 
    890     def RegisterModelChangeNotification(self, callback):
    891         self._modelChangeCallbacks.append(callback)
    892 
    893     def UnregisterModelChangeNotification(self, callback):
    894         if callback in self._modelChangeCallbacks:
    895             self._modelChangeCallbacks.remove(callback)
    896 
    897     def LockFromRelease(self):
    898         self.lockedFromRelease = True
    899 
    900     def UnlockFromRelease(self, scene):
    901         self.lockedFromRelease = False
    902         if self.released:
    903             self.ClearAudio()
    904             self.ClearAndRemoveAllModels(scene)