class SpaceObject(decometaclass.WrapBlueClass('destiny.ClientBall')):
    __persistdeco__ = 0
    __update_on_reload__ = 1
    _animationStates = None

    def __init__(self):
        self.explodeOnRemove = False
        self.exploded = False
        self.model = None
        self.animationSequencer = None
        self.released = False
        self.wreckID = None
        self._audioEntities = []
        self._audioEntity = None
        self.logger = logging.getLogger('spaceObject.' +
                                        self.__class__.__name__)
        self.modelLoadedEvent = locks.Event()
        self.explosionModel = None
        self.typeID = None
        self.typeData = {}
        self.explosionManager = ExplosionManager()

    @classmethod
    def GetDefaultAnimationName(cls):
        if cls._animationStates is None:
            return 'NormalLoop'
        states = blue.resMan.LoadObject(cls._animationStates)
        return states.defaultAnimation

    def Log(self, level, *args):
        try:
            self.logger.log(level, ' '.join(map(strx, args)))
        except TypeError:
            self.logger.log('[X]'.join(map(strx, args)).replace('\x00', '\\0'))

    def LogInfo(self, *args):
        self.Log(logging.DEBUG, '[', self.id, ']', *args)

    def LogWarn(self, *args):
        self.Log(logging.WARN, '[', self.id, ']', *args)

    def LogError(self, *args):
        self.Log(logging.ERROR, '[', self.id, ']', *args)

    def SetServices(self, spaceMgr, serviceMgr):
        self.spaceMgr = spaceMgr
        self.sm = serviceMgr
        self.spaceObjectFactory = serviceMgr.GetService(
            'sofService').spaceObjectFactory

    def Prepare(self):
        self.typeID = self.typeData.get('typeID', None)
        self.LoadModel()
        self.Assemble()

    def HasBlueInterface(self, obj, interfaceName):
        if hasattr(obj, 'TypeInfo'):
            return interfaceName in obj.TypeInfo()[1]
        return False

    def _GetComponentRegistry(self):
        return self.ballpark.componentRegistry

    def TriggerAnimation(self, state):
        if self.animationSequencer is None:
            return
        self.animationSequencer.GoToState(state)

    def GetCurrentAnimationState(self):
        if self.animationSequencer is None:
            return
        if self.animationSequencer.currentState is None:
            return
        return self.animationSequencer.currentState.name

    def GetModel(self):
        if not self.model:
            self.modelLoadedEvent.wait()
        return self.model

    def _LoadModelResource(self, fileName):
        self.LogInfo('LoadModel', fileName)
        model = None
        sofDNA = gfxutils.BuildSOFDNAFromTypeID(self.typeData['typeID'])
        if sofDNA is not None:
            model = self.spaceObjectFactory.BuildFromDNA(sofDNA)
        elif fileName is not None and len(fileName):
            model = blue.resMan.LoadObject(fileName)
        if model is None:
            self.LogError(
                'Error: Object type %s has invalid graphicFile, using graphicID: %s'
                % (self.typeData['typeID'], self.typeData['graphicID']))
        return model

    def LoadModel(self, fileName=None, loadedModel=None):
        if loadedModel:
            model = loadedModel
        else:
            if fileName is None:
                fileName = self.typeData.get('graphicFile')
            model = self._LoadModelResource(fileName)
        if self.released:
            return
        if not model:
            self.LogError('Could not load model for spaceobject. FileName:',
                          fileName, ' id:', self.id, ' typeID:',
                          getattr(self, 'typeID', '?'))
            return
        self.model = model
        if not hasattr(model, 'translationCurve'):
            self.LogError('LoadModel - Model in', fileName,
                          "doesn't have a translationCurve.")
        elif isinstance(self, blue.BlueWrapper):
            model.translationCurve = self
            model.rotationCurve = self
        model.name = '%d' % self.id
        if hasattr(model, 'useCurves'):
            model.useCurves = 1
        if self.model is not None and self.HasBlueInterface(
                self.model, 'IEveSpaceObject2'):
            scene = self.spaceMgr.GetScene()
            if scene is not None:
                scene.objects.append(self.model)
        else:
            raise RuntimeError('Invalid object loaded by spaceObject: %s' %
                               str(self.model))
        if self._animationStates is not None:
            self.animationSequencer = blue.resMan.LoadObject(
                self._animationStates)
            self.model.animationSequencer = self.animationSequencer
        self.sm.GetService('FxSequencer').NotifyModelLoaded(self.id)
        self.modelLoadedEvent.set()

    def Assemble(self):
        pass

    def SetStaticRotation(self):
        if self.model is None:
            return
        self.model.rotationCurve = None
        rot = self.typeData.get('dunRotation', None)
        if rot:
            yaw, pitch, roll = map(math.radians, rot)
            quat = geo2.QuaternionRotationSetYawPitchRoll(yaw, pitch, roll)
            if hasattr(self.model, 'rotation'):
                if type(self.model.rotation) == types.TupleType:
                    self.model.rotation = quat
                else:
                    self.model.rotation.SetYawPitchRoll(yaw, pitch, roll)
            else:
                self.model.rotationCurve = trinity.TriRotationCurve()
                self.model.rotationCurve.value = quat

    def _FindClosestBallDir(self, constgrp):
        bp = self.sm.StartService('michelle').GetBallpark()
        dist = 1e+100
        closestID = None
        for ballID, slimItem in bp.slimItems.iteritems():
            if slimItem.groupID == constgrp:
                test = bp.DistanceBetween(self.id, ballID)
                if test < dist:
                    dist = test
                    closestID = ballID

        if closestID is None:
            return (1.0, 0.0, 0.0)
        ball = bp.GetBall(closestID)
        direction = geo2.Vec3SubtractD((self.x, self.y, self.z),
                                       (ball.x, ball.y, ball.z))
        return direction

    def FindClosestMoonDir(self):
        return self._FindClosestBallDir(const.groupMoon)

    def FindClosestPlanetDir(self):
        """Locates the closet planet within reason and returns a direction to it"""
        return self._FindClosestBallDir(const.groupPlanet)

    def GetStaticDirection(self):
        """
            Override this method to define where an orbital object should
            align itself.
        
            I have ported the old ugly block of logic here to avoid having
            to perform trivial changes to a bunch of other objects.
        """
        return self.typeData.get('dunDirection', None)

    def SetStaticDirection(self):
        if self.model is None:
            return
        self.model.rotationCurve = None
        direction = self.GetStaticDirection()
        if direction is None:
            self.LogError(
                'Space object', self.id,
                'has no static direction defined - no rotation will be applied'
            )
            return
        self.AlignToDirection(direction)

    def AlignToDirection(self, direction):
        """Align the space object to a direction."""
        if not self.model:
            return
        zaxis = direction
        if geo2.Vec3LengthSqD(zaxis) > 0.0:
            zaxis = geo2.Vec3NormalizeD(zaxis)
            xaxis = geo2.Vec3CrossD(zaxis, (0, 1, 0))
            if geo2.Vec3LengthSqD(xaxis) == 0.0:
                zaxis = geo2.Vec3AddD(zaxis, mathCommon.RandomVector(0.0001))
                zaxis = geo2.Vec3NormalizeD(zaxis)
                xaxis = geo2.Vec3CrossD(zaxis, (0, 1, 0))
            xaxis = geo2.Vec3NormalizeD(xaxis)
            yaxis = geo2.Vec3CrossD(xaxis, zaxis)
        else:
            self.LogError('Space object', self.id, 'has invalid direction (',
                          direction, '). Unable to rotate it.')
            return
        mat = ((xaxis[0], xaxis[1], xaxis[2], 0.0), (yaxis[0], yaxis[1],
                                                     yaxis[2], 0.0),
               (-zaxis[0], -zaxis[1], -zaxis[2], 0.0), (0.0, 0.0, 0.0, 1.0))
        quat = geo2.QuaternionRotationMatrix(mat)
        if hasattr(self.model, 'modelRotationCurve'):
            if not self.model.modelRotationCurve:
                self.model.modelRotationCurve = trinity.TriRotationCurve(
                    0.0, 0.0, 0.0, 1.0)
            self.model.modelRotationCurve.value = quat
        else:
            self.model.rotationCurve = None

    def UnSync(self):
        if self.model is None:
            return
        startTime = long(random.random() * 123456.0 * 1234.0)
        scaling = 0.95 + random.random() * 0.1
        curves = timecurves.ReadCurves(self.model)
        timecurves.ResetTimeCurves(curves, startTime, scaling)

    def Display(self, display=1, canYield=True):
        if self.model is None:
            self.LogWarn('Display - No model')
            return
        if canYield:
            blue.synchro.Yield()
        if eve.session.shipid == self.id and display and self.IsCloaked():
            self.sm.StartService('FxSequencer').OnSpecialFX(
                self.id, None, None, None, None, 'effects.CloakNoAmim', 0, 1,
                0, 5, 0)
            return
        if self.model:
            self.model.display = display

    def IsCloaked(self):
        return self.isCloaked

    def OnDamageState(self, damageState):
        pass

    def GetDamageState(self):
        return self.spaceMgr.ballpark.GetDamageState(self.id)

    def DoFinalCleanup(self):
        """
        This is our last chance to clean up anything from this ball, called from Destiny
        as it removes it from the ballpark
        """
        if not self.sm.IsServiceRunning('FxSequencer'):
            return
        self.sm.GetService('FxSequencer').RemoveAllBallActivations(self.id)
        self.ClearExplosion()
        if not self.released:
            self.explodeOnRemove = False
            self.Release()

    def ClearExplosion(self, model=None):
        """
        Called by the explosion manager in case there are special references
        in the explosion that need cleaning up.
        """
        if hasattr(self, 'gfx') and self.gfx is not None:
            self.RemoveAndClearModel(self.gfx)
            self.gfx = None
        if self.explosionModel is not None:
            if getattr(self, 'explosionDisplayBinding', False):
                self.explosionDisplayBinding.destinationObject = None
                self.explosionDisplayBinding = None
            self.RemoveAndClearModel(self.explosionModel)
            self.explosionModel = None

    def Release(self, origin=None):
        uthread2.StartTasklet(self._Release, origin)

    def _Release(self, origin=None):
        self.LogInfo('Release')
        if self.released:
            return
        self.released = True
        if self.explodeOnRemove:
            delay = self.Explode()
            if delay:
                delay = min(delay, 5000)
                blue.synchro.SleepSim(delay)
        self.Display(display=0, canYield=False)
        scene = self.spaceMgr.GetScene()
        uthread2.StartTasklet(self.RemoveAndClearModel, self.model, scene)
        if self.animationSequencer is not None:
            self.model.animationSequencer = None
            self.animationSequencer = None
        self._audioEntities = []
        self._audioEntity = None
        self.model = None

    def RemoveAndClearModel(self, model, scene=None):
        """Remove the model from the scene and clear the transforms on it.
        
        :param model: The trinity model to clear.
        :param scene: The scene to remove the object from.
          If None, use the sceneManager to get the registered 
          default scene.
        """
        if model:
            self._Clearcurves(model)
        else:
            self.released = True
            return
        self.RemoveFromScene(model, scene)

    def _Clearcurves(self, model):
        """Clean up any references to the translation and rotation curves.
        
        :param model: The trinity model to clear.
        :param scene: The scene to remove the object from.
          If None, use the sceneManager to get the registered 
          default scene.
        """
        if hasattr(model, 'translationCurve'):
            model.translationCurve = None
            model.rotationCurve = None
        if hasattr(model, 'observers'):
            for ob in model.observers:
                ob.observer = None

    def RemoveFromScene(self, model, scene):
        """Remove the model from the objects list of the scene.
        
        :param model: The trinity model to clear.
        :param scene: The scene to remove the object from.
          If None, use the sceneManager to get the registered 
          default scene.
        """
        if scene is None:
            scene = self.spaceMgr.GetScene()
        if scene:
            scene.objects.fremove(model)

    def GetExplosionInfo(self):
        """
        This method builds an explosion path using the race.
        """
        raceName = self.typeData.get('sofRaceName', None)
        return eveSpaceObject.GetDeathExplosionInfo(self.model, self.radius,
                                                    raceName)

    def Explode(self,
                explosionURL=None,
                scaling=1.0,
                managed=False,
                delay=0.0):
        """
        Makes the spaceobject explode.
        Arguments:
            explosionURL is the path to the explosion asset
            scaling controls additionsl scaling of the explosion asset
            managed determines whether to use the explosionManager to manage the explosion
            delay is the delay with which to hide the exploding spaceobject during the explosion
        """
        self.LogInfo('Exploding')
        if self.exploded:
            return False
        self.sm.ScatterEvent('OnObjectExplode', self.GetModel())
        self.exploded = True
        delayedRemove = delay
        self.explodedTime = blue.os.GetTime()
        if gfxsettings.Get(gfxsettings.UI_EXPLOSION_EFFECTS_ENABLED):
            if managed:
                gfx = self.explosionManager.GetExplosion(
                    explosionURL, callback=self.ClearExplosion)
            else:
                if explosionURL is None:
                    self.LogError(
                        'explosionURL not set when calling Explode. Possibly wrongly authored content. typeID:',
                        self.typeID)
                    explosionURL, (delay, scaling) = self.GetExplosionInfo()
                explosionURL = explosionURL.replace('.blue', '.red').replace(
                    '/Effect/', '/Effect3/')
                gfx = trinity.Load(explosionURL)
                if not gfx:
                    self.LogError('Failed to load explosion: ', explosionURL,
                                  ' - using default')
                    gfx = trinity.Load(
                        'res:/Model/Effect3/Explosion/entityExplode_large.red')
                if gfx.__bluetype__ == 'trinity.EveEffectRoot':
                    msg = 'ExplosionManager circumvented, explosion not managed for %s. (Class:%s, Type:%s)'
                    self.LogWarn(
                        msg %
                        (explosionURL, self.__class__.__name__, self.typeID))
                    gfx.Start()
                elif gfx.__bluetype__ != 'trinity.EveRootTransform':
                    root = trinity.EveRootTransform()
                    root.children.append(gfx)
                    root.name = explosionURL
                    gfx = root
            gfx.translationCurve = self
            self.explosionModel = gfx
            scale = scaling
            gfx.scaling = (gfx.scaling[0] * scale, gfx.scaling[1] * scale,
                           gfx.scaling[2] * scale)
            scene = self.spaceMgr.GetScene()
            scene.objects.append(gfx)
        if self.wreckID is not None:
            wreckBall = self.sm.StartService('michelle').GetBall(self.wreckID)
            if wreckBall is not None:
                uthread2.StartTasklet(wreckBall.DisplayWreck, delayedRemove)
        return delayedRemove

    def GetEventNameFromSlimItem(self, defaultSoundUrl):
        slimItem = self.typeData.get('slimItem')
        eventName = spaceobjaudio.GetSoundUrl(slimItem, defaultSoundUrl)
        return eventName

    def SetupAmbientAudio(self, defaultSoundUrl=None):
        """
            Prepares any ambient audio effects authored on the graphics data.
        """
        audioUrl = self.GetEventNameFromSlimItem(defaultSoundUrl)
        if audioUrl is None:
            return
        audentity = self._GetGeneralAudioEntity()
        if audentity is not None:
            spaceobjaudio.PlayAmbientAudio(audentity, audioUrl)

    def SetupSharedAmbientAudio(self, defaultSoundUrl=None):
        """
            Prepares shared ambient audio effects authored on the graphics data.
        :param defaultSoundUrl: An audio event used if nothing is found in FSD
        """
        eventName = self.GetEventNameFromSlimItem(defaultSoundUrl)
        if eventName is None or self.model is None:
            return
        spaceobjaudio.SetupSharedEmitterForAudioEvent(self.model, eventName)

    def LookAtMe(self):
        pass

    def _GetGeneralAudioEntity(self, recreate=False):
        if self.model is None:
            self._audioEntity = None
            self.LogWarn('model is None, cannot play audio.')
        elif recreate or self._audioEntity is None:
            self._audioEntity = spaceobjaudio.SetupAudioEntity(self.model)
            self._audioEntities.append(self._audioEntity)
        return self._audioEntity

    def PlayGeneralAudioEvent(self, eventName):
        audentity = self._GetGeneralAudioEntity()
        if audentity is not None:
            spaceobjaudio.SendEvent(audentity, eventName)

    def GetNamedAudioEmitterFromObservers(self, emitterName):
        if getattr(self, 'model', None) is None:
            return
        for triObserver in self.model.observers:
            if triObserver.observer.name.lower() == emitterName:
                return triObserver.observer
Esempio n. 2
0
class SpaceObject(decometaclass.WrapBlueClass('destiny.ClientBall')):
    __persistdeco__ = 0
    __update_on_reload__ = 1

    def __init__(self):
        self.explodeOnRemove = False
        self.exploded = False
        self.unloaded = False
        self.model = None
        self.additionalModels = []
        self.animationSequencer = None
        self.animationStateObject = None
        self.released = False
        self.wreckID = None
        self._audioEntities = []
        self._audioEntity = None
        self.logger = logging.getLogger('spaceObject.' +
                                        self.__class__.__name__)
        self.logger = SpaceObjectLogAdapter(self.logger, so_id=self.id)
        self.modelLoadedEvent = locks.Event()
        self.modelLoadSignal = Signal()
        self.explosionModel = None
        self.typeID = None
        self.typeData = {}
        self.explosionManager = ExplosionManager()

    def GetPositionCurve(self):
        return self

    def GetTypeID(self):
        if self.typeID is None:
            self.typeID = self.typeData.get('typeID', None)
        return self.typeID

    def SetServices(self, spaceMgr, serviceMgr):
        self.spaceMgr = spaceMgr
        self.sm = serviceMgr
        self.spaceObjectFactory = serviceMgr.GetService(
            'sofService').spaceObjectFactory

    def Prepare(self):
        self.typeID = self.typeData.get('typeID', None)
        self.LoadModel()
        self.Assemble()

    def HasBlueInterface(self, obj, interfaceName):
        if hasattr(obj, 'TypeInfo'):
            return interfaceName in obj.TypeInfo()[1]
        return False

    def _GetComponentRegistry(self):
        return self.ballpark.componentRegistry

    def TriggerAnimation(self, state, **kwargs):
        if self.animationSequencer is None:
            return
        self.logger.debug(
            'SpaceObject: Trigger animation %s with parameters %s', state,
            kwargs)
        for parameterName, parameterValue in kwargs.iteritems():
            self.animationSequencer.SetStateParameter(state, parameterName,
                                                      parameterValue)

        self.animationSequencer.GoToState(state)
        if self.animationStateObject:
            self.RemoveAndClearModel(self.animationStateObject)
            self.animationStateObject = None
        if state in self.typeData['animationStateObjects']:
            dnaToLoad = self.typeData['animationStateObjects'][state]
            self.animationStateObject = self.spaceObjectFactory.BuildFromDNA(
                dnaToLoad)
            self._SetupModelAttributes(self.animationStateObject,
                                       '%d_%s' % (self.id, state))
            self.animationStateObject.rotationCurve = self.model.rotationCurve
            self._AddModelToScene(self.animationStateObject)

    def GetCurrentAnimationState(self, stateMachineName):
        if self.animationSequencer is None:
            return
        for stateMachine in self.animationSequencer.stateMachines:
            if stateMachine.name == stateMachineName:
                if stateMachine.currentState is None:
                    return
                else:
                    return stateMachine.currentState.name

    def GetModel(self):
        if not self.model:
            if blue.os.isOnMainTasklet:
                return None
            self.modelLoadedEvent.wait()
        return self.model

    def GetDNA(self):
        materialSetID = self.typeData.get('slimItem').skinMaterialSetID
        return gfxutils.BuildSOFDNAFromTypeID(self.typeData['typeID'],
                                              materialSetID=materialSetID)

    def _LoadModelResource(self, fileName=None):
        self.logger.debug('LoadModel: %s', fileName)
        model = None
        sofDNA = self.GetDNA()
        self.logger.debug("LoadModel fileName='%s' sofDNA='%s'", fileName,
                          sofDNA)
        if sofDNA is not None and fileName is None:
            model = self.spaceObjectFactory.BuildFromDNA(sofDNA)
        else:
            if fileName is None:
                fileName = self.typeData.get('graphicFile')
            if fileName is not None and len(fileName):
                model = blue.resMan.LoadObject(fileName)
        if model is None:
            self.logger.error(
                'Error: Object type %s has invalid graphicFile, using graphicID: %s',
                self.typeData['typeID'], self.typeData['graphicID'])
        return model

    def _SetupModelAndAddToScene(self, fileName=None, loadedModel=None):
        if loadedModel:
            model = loadedModel
        else:
            model = self._LoadModelResource(fileName)
        if self.released:
            return None
        if not model:
            self.logger.error(
                'Could not load model for spaceobject. FileName:%s typeID:%s',
                fileName, getattr(self, 'typeID', '?'))
            return None
        self._SetupModelAttributes(model, '%d' % self.id)
        self._AddModelToScene(model)
        return model

    def _SetupModelAttributes(self, model, objectName):
        model.translationCurve = self
        model.rotationCurve = self
        model.name = objectName
        if hasattr(model, 'useCurves'):
            model.useCurves = 1
        if model and hasattr(model, 'albedoColor'):
            model.albedoColor = eveSpaceObject.GetAlbedoColor(model)

    def _AddModelToScene(self, model):
        if model is not None:
            scene = self.spaceMgr.GetScene()
            if scene is not None:
                scene.objects.append(model)
            else:
                raise RuntimeError('Invalid object loaded by spaceObject: %s' %
                                   str(model))

    def LoadAdditionalModel(self, fileName=None):
        model = self._SetupModelAndAddToScene(fileName)
        if fileName is not None:
            self.additionalModels.append(model)
        return model

    def NotifyModelLoaded(self):
        if self.model is not None:
            self.logger.debug('SpaceObject - NotifyModelLoaded')
            self.modelLoadedEvent.set()
            self.modelLoadSignal()
            self.sm.GetService('FxSequencer').NotifyModelLoaded(self.id)
        else:
            self.logger.warning(
                'SpaceObject - NotifyModelLoaded called without a model present, no notification was done'
            )

    def RegisterForModelLoad(self, func):
        self.modelLoadSignal.connect(func)

    def LoadModel(self, fileName=None, loadedModel=None):
        self.model = self._SetupModelAndAddToScene(fileName, loadedModel)
        if self.model is None:
            return
        self.SetupAnimationInformation(self.model)
        self.NotifyModelLoaded()

    def SetupAnimationInformation(self, model):
        self._SetupAnimationStateMachines(model)
        self._SetupAnimationUpdater(model)

    def SetAnimationSequencer(self, model):
        if model is not None and hasattr(model, 'animationSequencer'):
            self.animationSequencer = model.animationSequencer
        else:
            self.animationSequencer = None

    def _SetupAnimationStateMachines(self, model):
        animationStates = self.typeData['animationStates']
        if len(animationStates) == 0:
            return
        spaceobjanimation.LoadAnimationStates(animationStates,
                                              cfg.graphicStates, model,
                                              trinity)
        self.SetAnimationSequencer(model)

    def _SetupAnimationUpdater(self, model):
        if not hasattr(
                model,
                'animationUpdater') or not self.typeData['animationStates']:
            return
        if self._audioEntity is None:
            self._audioEntity = self._GetGeneralAudioEntity(model=model)
        if model is not None and model.animationUpdater is not None:
            model.animationUpdater.eventListener = self._audioEntity

    def Assemble(self):
        pass

    def GetStaticRotation(self):
        rot = self.typeData.get('dunRotation', None)
        if rot:
            yaw, pitch, roll = map(math.radians, rot)
            return geo2.QuaternionRotationSetYawPitchRoll(yaw, pitch, roll)
        else:
            return (0.0, 0.0, 0.0, 1.0)

    def SetStaticRotation(self):
        if self.model is None:
            return
        self.model.rotationCurve = None
        rot = self.typeData.get('dunRotation', None)
        if rot:
            yaw, pitch, roll = map(math.radians, rot)
            quat = geo2.QuaternionRotationSetYawPitchRoll(yaw, pitch, roll)
            if hasattr(self.model, 'rotation'):
                if type(self.model.rotation) == types.TupleType:
                    self.model.rotation = quat
                else:
                    self.model.rotation.SetYawPitchRoll(yaw, pitch, roll)
            else:
                self.model.rotationCurve = trinity.TriRotationCurve()
                self.model.rotationCurve.value = quat
                if self.animationStateObject is not None:
                    self.animationStateObject.rotationCurve = self.model.rotationCurve

    def _FindClosestBallDir(self, constgrp):
        bp = self.sm.StartService('michelle').GetBallpark()
        dist = 1e+100
        closestID = None
        for ballID, slimItem in bp.slimItems.iteritems():
            if slimItem.groupID == constgrp:
                test = bp.DistanceBetween(self.id, ballID)
                if test < dist:
                    dist = test
                    closestID = ballID

        if closestID is None:
            return (1.0, 0.0, 0.0)
        ball = bp.GetBall(closestID)
        direction = geo2.Vec3SubtractD((self.x, self.y, self.z),
                                       (ball.x, ball.y, ball.z))
        return direction

    def FindClosestMoonDir(self):
        return self._FindClosestBallDir(const.groupMoon)

    def FindClosestPlanetDir(self):
        return self._FindClosestBallDir(const.groupPlanet)

    def GetStaticDirection(self):
        return self.typeData.get('dunDirection', None)

    def SetStaticDirection(self):
        if self.model is None:
            return
        self.model.rotationCurve = None
        direction = self.GetStaticDirection()
        if direction is None:
            self.logger.error(
                'No static direction defined - no rotation will be applied')
            return
        self.AlignToDirection(direction)

    def AlignToDirection(self, direction):
        if not self.model:
            return
        zaxis = direction
        if geo2.Vec3LengthSqD(zaxis) > 0.0:
            zaxis = geo2.Vec3NormalizeD(zaxis)
            xaxis = geo2.Vec3CrossD(zaxis, (0, 1, 0))
            if geo2.Vec3LengthSqD(xaxis) == 0.0:
                zaxis = geo2.Vec3AddD(zaxis, mathCommon.RandomVector(0.0001))
                zaxis = geo2.Vec3NormalizeD(zaxis)
                xaxis = geo2.Vec3CrossD(zaxis, (0, 1, 0))
            xaxis = geo2.Vec3NormalizeD(xaxis)
            yaxis = geo2.Vec3CrossD(xaxis, zaxis)
        else:
            self.logger.error('Invalid direction (%s). Unable to rotate it.',
                              direction)
            return
        mat = ((xaxis[0], xaxis[1], xaxis[2], 0.0), (yaxis[0], yaxis[1],
                                                     yaxis[2], 0.0),
               (-zaxis[0], -zaxis[1], -zaxis[2], 0.0), (0.0, 0.0, 0.0, 1.0))
        quat = geo2.QuaternionRotationMatrix(mat)
        if hasattr(self.model, 'modelRotationCurve'):
            if not self.model.modelRotationCurve:
                self.model.modelRotationCurve = trinity.TriRotationCurve(
                    0.0, 0.0, 0.0, 1.0)
            self.model.modelRotationCurve.value = quat
        else:
            self.model.rotationCurve = None

    def UnSync(self):
        if self.model is None:
            return
        startTime = long(random.random() * 123456.0 * 1234.0)
        scaling = 0.95 + random.random() * 0.1
        curves = timecurves.ReadCurves(self.model)
        timecurves.ResetTimeCurves(curves, startTime, scaling)

    def Display(self, display=1, canYield=True):
        if self.model is None:
            if display:
                self.logger.warning('Display - No model')
            return
        if canYield:
            blue.synchro.Yield()
        if eve.session.shipid == self.id and display and self.IsCloaked():
            self.sm.StartService('FxSequencer').OnSpecialFX(
                self.id, None, None, None, None, 'effects.CloakNoAmim', 0, 1,
                0, 5, 0)
            return
        if self.model:
            self.model.display = display

    def IsCloaked(self):
        return self.isCloaked

    def OnDamageState(self, damageState):
        pass

    def GetDamageState(self):
        bp = sm.GetService('michelle').GetBallpark()
        if bp is not None:
            return bp.GetDamageState(self.id)

    def _UpdateImpacts(self):
        states = self.GetDamageState()
        if states is not None and self.model is not None:
            damageState = [(d if d is not None else 0.0) for d in states]
            self.model.SetImpactDamageState(damageState[0], damageState[1],
                                            damageState[2], True)

    def DoFinalCleanup(self):
        if not self.sm.IsServiceRunning('FxSequencer'):
            return
        self.sm.GetService('FxSequencer').RemoveAllBallActivations(self.id)
        self.ClearExplosion()
        if not self.released:
            self.explodeOnRemove = False
            self.Release()
        elif self.HasModels():
            scene = self.spaceMgr.GetScene()
            self.ClearAndRemoveAllModels(scene)

    def ClearExplosion(self, model=None):
        if hasattr(self, 'gfx') and self.gfx is not None:
            self.RemoveAndClearModel(self.gfx)
            self.gfx = None
        if self.explosionModel is not None:
            if getattr(self, 'explosionDisplayBinding', False):
                self.explosionDisplayBinding.destinationObject = None
                self.explosionDisplayBinding = None
            self.RemoveAndClearModel(self.explosionModel)
            self.explosionModel = None

    def Release(self, origin=None):
        uthread2.StartTasklet(self._Release, origin)

    def _Release(self, origin=None):
        if self.released:
            return
        self.released = True
        if self.explodeOnRemove:
            delay = self.Explode()
            if delay:
                blue.synchro.SleepSim(delay)
        self.Display(display=0, canYield=False)
        for model in self.additionalModels:
            if model is not None:
                model.display = False

        if hasattr(self.model, 'animationSequencer'):
            self.model.animationSequencer = None
        self.animationSequencer = None
        if hasattr(self.model, 'animationUpdater'
                   ) and self.model.animationUpdater is not None:
            self.model.animationUpdater.eventListener = None
        self._audioEntities = []
        self._audioEntity = None
        scene = self.spaceMgr.GetScene()
        camera = sm.GetService('sceneManager').GetActiveSpaceCamera()
        lookingAt = camera.GetLookAtItemID()
        interestID = camera.GetTrackItemID()
        if self.explodeOnRemove and (self.id == lookingAt
                                     or interestID == self.id):
            self.RemoveAllModelsFromScene(scene)
        else:
            self.ClearAndRemoveAllModels(scene)

    def HasModels(self):
        return self.model is not None

    def ClearAndRemoveAllModels(self, scene):
        self.RemoveAndClearModel(self.model, scene)
        self.model = None
        for m in self.additionalModels:
            self.RemoveAndClearModel(m, scene)

        self.additionalModels = []
        if self.animationStateObject is not None:
            self.RemoveAndClearModel(self.animationStateObject, scene)
        self.animationStateObject = None

    def RemoveAllModelsFromScene(self, scene):
        if scene is None:
            return
        scene.objects.fremove(self.model)
        for m in self.additionalModels:
            scene.objects.fremove(m)

        if self.animationStateObject is not None:
            scene.objects.fremove(self.animationStateObject)

    def RemoveAndClearModel(self, model, scene=None):
        if model:
            self._Clearcurves(model)
        else:
            self.released = True
            return
        self.RemoveFromScene(model, scene)

    def _Clearcurves(self, model):
        if hasattr(model, 'translationCurve'):
            model.translationCurve = None
            model.rotationCurve = None
        if hasattr(model, 'observers'):
            for ob in model.observers:
                ob.observer = None

    def RemoveFromScene(self, model, scene):
        if scene is None:
            scene = self.spaceMgr.GetScene()
        if scene:
            scene.objects.fremove(model)

    def GetExplosionInfo(self):
        raceName = self.typeData.get('sofRaceName', None)
        return eveSpaceObject.GetDeathExplosionInfo(self.model, self.radius,
                                                    raceName)

    def GetExplosionLookAtDelay(self):
        return eveSpaceObject.GetDeathExplosionLookDelay(
            self.model, self.radius)

    def Explode(self,
                explosionURL=None,
                scaling=1.0,
                managed=False,
                delay=0.0):
        if self.exploded:
            return False
        self.sm.ScatterEvent('OnObjectExplode', self.GetModel())
        self.exploded = True
        delayedRemove = delay
        self.explodedTime = blue.os.GetTime()
        if gfxsettings.Get(gfxsettings.UI_EXPLOSION_EFFECTS_ENABLED):
            if SpaceObjectExplosionManager.USE_EXPLOSION_BUCKETS:
                explosionBucket = fsdExplosionBuckets.GetExplosionBucketByTypeID(
                    self.typeData['typeID'])
                if explosionBucket:
                    self.logger.debug('Exploding with explosion bucket')
                    scene = sm.GetService('space').GetScene()
                    wreckSwitchTime, _, __ = SpaceObjectExplosionManager.ExplodeBucketForBall(
                        self, scene)
                    return wreckSwitchTime
            if managed:
                gfx = self.explosionManager.GetExplosion(
                    explosionURL, callback=self.ClearExplosion)
            else:
                if explosionURL is None:
                    self.logger.error(
                        'explosionURL not set when calling Explode. Possibly wrongly authored content. typeID: %s',
                        self.typeID)
                    explosionURL, (delay, scaling) = self.GetExplosionInfo()
                explosionURL = explosionURL.replace('.blue', '.red').replace(
                    '/Effect/', '/Effect3/')
                gfx = trinity.Load(explosionURL)
                if not gfx:
                    self.logger.error(
                        'Failed to load explosion: %s - using default',
                        explosionURL)
                    gfx = trinity.Load(
                        'res:/Model/Effect3/Explosion/entityExplode_large.red')
                if isinstance(gfx, trinity.EveEffectRoot2):
                    msg = 'ExplosionManager circumvented, explosion not managed for %s. (Class:%s, Type:%s)'
                    self.logger.warning(msg, explosionURL,
                                        self.__class__.__name__, self.typeID)
                    gfx.Start()
                elif not isinstance(
                        gfx,
                    (trinity.EveRootTransform, trinity.EveEffectRoot2)):
                    root = trinity.EveRootTransform()
                    root.children.append(gfx)
                    root.name = explosionURL
                    gfx = root
            gfx.translationCurve = self
            self.explosionModel = gfx
            scale = scaling
            gfx.scaling = (gfx.scaling[0] * scale, gfx.scaling[1] * scale,
                           gfx.scaling[2] * scale)
            scene = self.spaceMgr.GetScene()
            if scene is not None:
                scene.objects.append(gfx)
        if self.wreckID is not None:
            wreckBall = self.sm.StartService('michelle').GetBall(self.wreckID)
            if wreckBall is not None:
                uthread2.StartTasklet(wreckBall.DisplayWreck, delayedRemove)
        return delayedRemove

    def PrepareForFiring(self):
        pass

    def GetEventNameFromSlimItem(self, defaultSoundUrl):
        slimItem = self.typeData.get('slimItem')
        eventName = spaceobjaudio.GetSoundUrl(slimItem, defaultSoundUrl)
        return eventName

    def SetupAmbientAudio(self, defaultSoundUrl=None):
        audioUrl = self.GetEventNameFromSlimItem(defaultSoundUrl)
        if audioUrl is None:
            return
        audentity = self._GetGeneralAudioEntity()
        if audentity is not None:
            spaceobjaudio.PlayAmbientAudio(audentity, audioUrl)

    def SetupSharedAmbientAudio(self, defaultSoundUrl=None):
        eventName = self.GetEventNameFromSlimItem(defaultSoundUrl)
        if eventName is None or self.model is None:
            return
        spaceobjaudio.SetupSharedEmitterForAudioEvent(self.model, eventName)

    def LookAtMe(self):
        pass

    def _GetGeneralAudioEntity(self, model=None, recreate=False):
        if model is None:
            model = self.model
        if model is None:
            self._audioEntity = None
            self.logger.warning('model is None, cannot play audio.')
        elif recreate or self._audioEntity is None:
            self._audioEntity = spaceobjaudio.SetupAudioEntity(model)
            self._audioEntities.append(self._audioEntity)
        return self._audioEntity

    def PlayGeneralAudioEvent(self, eventName):
        audentity = self._GetGeneralAudioEntity()
        if audentity is not None:
            spaceobjaudio.SendEvent(audentity, eventName)

    def GetNamedAudioEmitterFromObservers(self, emitterName):
        if getattr(self, 'model', None) is None:
            return
        for triObserver in self.model.observers:
            if triObserver.observer.name.lower() == emitterName:
                return triObserver.observer

    def PlaySound(self, event):
        if self.model is None:
            return
        if hasattr(self.model, 'observers'):
            for obs in self.model.observers:
                obs.observer.SendEvent(unicode(event))
                return

        self.logger.error(
            "Space Object: %s can't play sound. Sound observer not found.",
            self.typeData.get('typeName', None))