예제 #1
0
class AvatarInputHandler(CallbackDelayer, ComponentSystem):
    bootcampCtrl = dependency.descriptor(IBootcampController)
    ctrl = property(lambda self: self.__curCtrl)
    ctrls = property(lambda self: self.__ctrls)
    isSPG = property(lambda self: self.__isSPG)
    isATSPG = property(lambda self: self.__isATSPG)
    isFlashBangAllowed = property(lambda self: self.__ctrls['video'] != self.__curCtrl)
    isDetached = property(lambda self: self.__isDetached)
    isGuiVisible = property(lambda self: self.__isGUIVisible)
    isStarted = property(lambda self: self.__isStarted)
    isObserverFPV = property(lambda self: BigWorld.player().isObserver() and BigWorld.player().isObserverFPV)
    remoteCameraSender = property(lambda self: self.__remoteCameraSender)
    __ctrlModeName = aih_global_binding.bindRW(_BINDING_ID.CTRL_MODE_NAME)
    __aimOffset = aih_global_binding.bindRW(_BINDING_ID.AIM_OFFSET)
    _DYNAMIC_CAMERAS_ENABLED_KEY = 'global/dynamicCameraEnabled'
    settingsCore = dependency.descriptor(ISettingsCore)

    @staticmethod
    def enableDynamicCamera(enable, useHorizontalStabilizer=True):
        for dynamicCameraClass in _DYNAMIC_CAMERAS:
            dynamicCameraClass.enableDynamicCamera(enable)

        SniperAimingSystem.setStabilizerSettings(useHorizontalStabilizer, True)

    @staticmethod
    def isCameraDynamic():
        for dynamicCameraClass in _DYNAMIC_CAMERAS:
            if not dynamicCameraClass.isCameraDynamic():
                return False

        return True

    @staticmethod
    def isSniperStabilized():
        return SniperAimingSystem.getStabilizerSettings()

    @property
    def ctrlModeName(self):
        return self.__ctrlModeName

    siegeModeControl = ComponentDescriptor()
    siegeModeSoundNotifications = ComponentDescriptor()
    steadyVehicleMatrixCalculator = ComponentDescriptor()

    def __init__(self):
        CallbackDelayer.__init__(self)
        ComponentSystem.__init__(self)
        self.__alwaysShowAimKey = None
        self.__showMarkersKey = None
        sec = self._readCfg()
        self.onCameraChanged = Event()
        self.onPostmortemVehicleChanged = Event()
        self.onPostmortemKillerVisionEnter = Event()
        self.onPostmortemKillerVisionExit = Event()
        self.__isArenaStarted = False
        self.__isStarted = False
        self.__targeting = _Targeting()
        self.__vertScreenshotCamera = _VertScreenshotCamera()
        self.__ctrls = dict()
        self.__killerVehicleID = None
        self.__isAutorotation = True
        self.__prevModeAutorotation = None
        self.__isSPG = False
        self.__isATSPG = False
        self.__setupCtrls(sec)
        self.__curCtrl = self.__ctrls[_CTRLS_FIRST]
        self.__ctrlModeName = _CTRLS_FIRST
        self.__isDetached = False
        self.__waitObserverCallback = None
        self.__observerVehicle = None
        self.__observerIsSwitching = False
        self.__commands = []
        self.__remoteCameraSender = None
        self.__isGUIVisible = False
        return

    def __constructComponents(self):
        player = BigWorld.player()
        if player.vehicleTypeDescriptor.hasSiegeMode:
            if not self.siegeModeControl:
                self.siegeModeControl = SiegeModeControl()
            self.__commands.append(self.siegeModeControl)
            self.siegeModeControl.onSiegeStateChanged += lambda *args: self.steadyVehicleMatrixCalculator.relinkSources()
            self.siegeModeSoundNotifications = SiegeModeSoundNotifications()
            self.siegeModeControl.onSiegeStateChanged += self.siegeModeSoundNotifications.onSiegeStateChanged
            self.siegeModeControl.onRequestFail += self.__onRequestFail
            self.siegeModeControl.onSiegeStateChanged += SiegeModeCameraShaker.shake
        if self.bootcampCtrl.isInBootcamp() and constants.HAS_DEV_RESOURCES:
            self.__commands.append(BootcampModeControl())

    def prerequisites(self):
        out = []
        for ctrl in self.__ctrls.itervalues():
            out += ctrl.prerequisites()

        return out

    def handleKeyEvent(self, event):
        import game
        isDown, key, mods, isRepeat = game.convertKeyEvent(event)
        if isRepeat:
            return False
        elif self.__isStarted and self.__isDetached:
            if self.__curCtrl.alwaysReceiveKeyEvents() and not self.isObserverFPV or CommandMapping.g_instance.isFired(CommandMapping.CMD_CM_LOCK_TARGET, key):
                self.__curCtrl.handleKeyEvent(isDown, key, mods, event)
            return BigWorld.player().handleKey(isDown, key, mods)
        elif not self.__isStarted or self.__isDetached:
            return False
        for command in self.__commands:
            if command.handleKeyEvent(isDown, key, mods, event):
                return True

        if isDown and BigWorld.isKeyDown(Keys.KEY_CAPSLOCK):
            if self.__alwaysShowAimKey is not None and key == self.__alwaysShowAimKey:
                gui_event_dispatcher.toggleCrosshairVisibility()
                return True
            if self.__showMarkersKey is not None and key == self.__showMarkersKey and not self.__isGUIVisible:
                gui_event_dispatcher.toggleMarkers2DVisibility()
                return True
            if key == Keys.KEY_F5 and constants.HAS_DEV_RESOURCES:
                self.__vertScreenshotCamera.enable(not self.__vertScreenshotCamera.isEnabled)
                return True
        if key == Keys.KEY_SPACE and isDown and BigWorld.player().isObserver():
            BigWorld.player().cell.switchObserverFPV(not BigWorld.player().isObserverFPV)
            return True
        else:
            return True if not self.isObserverFPV and self.__curCtrl.handleKeyEvent(isDown, key, mods, event) else BigWorld.player().handleKey(isDown, key, mods)

    def handleMouseEvent(self, dx, dy, dz):
        return False if not self.__isStarted or self.__isDetached else self.__curCtrl.handleMouseEvent(dx, dy, dz)

    def setForcedGuiControlMode(self, flags):
        result = False
        detached = flags & GUI_CTRL_MODE_FLAG.CURSOR_ATTACHED > 0
        if detached ^ self.__isDetached:
            self.__isDetached = detached
            self.__targeting.detach(self.__isDetached)
            if detached:
                g_appLoader.attachCursor(settings.APP_NAME_SPACE.SF_BATTLE, flags=flags)
                result = True
                if flags & GUI_CTRL_MODE_FLAG.AIMING_ENABLED > 0:
                    self.setAimingMode(False, AIMING_MODE.USER_DISABLED)
            else:
                g_appLoader.detachCursor(settings.APP_NAME_SPACE.SF_BATTLE)
                result = True
            self.__curCtrl.setForcedGuiControlMode(detached)
        elif detached:
            g_appLoader.syncCursor(settings.APP_NAME_SPACE.SF_BATTLE, flags=flags)
        return result

    def updateShootingStatus(self, canShoot):
        return None if self.__isDetached else self.__curCtrl.updateShootingStatus(canShoot)

    def getDesiredShotPoint(self, ignoreAimingMode=False):
        return None if self.__isDetached else self.__curCtrl.getDesiredShotPoint(ignoreAimingMode)

    def getMarkerPoint(self):
        point = None
        if self.__ctrlModeName in (_CTRL_MODE.ARCADE, _CTRL_MODE.STRATEGIC, _CTRL_MODE.ARTY):
            AimingSystems.shootInSkyPoint.has_been_called = False
            point = self.getDesiredShotPoint(ignoreAimingMode=True)
            if AimingSystems.shootInSkyPoint.has_been_called:
                point = None
        return point

    def showGunMarker(self, isShown):
        self.__curCtrl.setGunMarkerFlag(isShown, _GUN_MARKER_FLAG.CLIENT_MODE_ENABLED)

    def showGunMarker2(self, isShown):
        if not BattleReplay.isPlaying():
            self.__curCtrl.setGunMarkerFlag(isShown, _GUN_MARKER_FLAG.SERVER_MODE_ENABLED)
            if gun_marker_ctrl.useDefaultGunMarkers():
                self.__curCtrl.setGunMarkerFlag(not isShown, _GUN_MARKER_FLAG.CLIENT_MODE_ENABLED)
            replayCtrl = BattleReplay.g_replayCtrl
            replayCtrl.setUseServerAim(isShown)

    def updateGunMarker(self, pos, direction, size, relaxTime, collData):
        self.__curCtrl.updateGunMarker(_GUN_MARKER_TYPE.CLIENT, pos, direction, size, relaxTime, collData)

    def updateGunMarker2(self, pos, direction, size, relaxTime, collData):
        self.__curCtrl.updateGunMarker(_GUN_MARKER_TYPE.SERVER, pos, direction, size, relaxTime, collData)

    def setAimingMode(self, enable, mode):
        self.__curCtrl.setAimingMode(enable, mode)

    def getAimingMode(self, mode):
        return self.__curCtrl.getAimingMode(mode)

    def setAutorotation(self, bValue):
        if not self.__curCtrl.enableSwitchAutorotationMode():
            return
        elif not BigWorld.player().isOnArena:
            return
        else:
            if self.__isAutorotation != bValue:
                self.__isAutorotation = bValue
                BigWorld.player().enableOwnVehicleAutorotation(self.__isAutorotation)
            self.__prevModeAutorotation = None
            return

    def getAutorotation(self):
        return self.__isAutorotation

    def switchAutorotation(self):
        self.setAutorotation(not self.__isAutorotation)

    def activatePostmortem(self, isRespawn):
        if self.siegeModeSoundNotifications is not None:
            self.siegeModeSoundNotifications = None
        BigWorld.player().autoAim(None)
        for ctlMode in self.__ctrls.itervalues():
            ctlMode.resetAimingMode()

        try:
            params = self.__curCtrl.postmortemCamParams
        except Exception:
            params = None

        onPostmortemActivation = getattr(self.__curCtrl, 'onPostmortemActivation', None)
        if onPostmortemActivation is not None:
            onPostmortemActivation(_CTRL_MODE.POSTMORTEM, postmortemParams=params, bPostmortemDelay=True, respawn=isRespawn)
        else:
            self.onControlModeChanged(_CTRL_MODE.POSTMORTEM, postmortemParams=params, bPostmortemDelay=True, respawn=isRespawn)
        return

    def deactivatePostmortem(self):
        self.onControlModeChanged('arcade')
        arcadeMode = self.__ctrls['arcade']
        arcadeMode.camera.setToVehicleDirection()
        self.__identifySPG()
        self.__constructComponents()

    def setKillerVehicleID(self, killerVehicleID):
        self.__killerVehicleID = killerVehicleID

    def getKillerVehicleID(self):
        return self.__killerVehicleID

    def start(self):
        g_guiResetters.add(self.__onRecreateDevice)
        self.steadyVehicleMatrixCalculator = SteadyVehicleMatrixCalculator()
        self.__identifySPG()
        self.__constructComponents()
        for control in self.__ctrls.itervalues():
            control.create()

        avatar = BigWorld.player()
        if not self.__curCtrl.isManualBind():
            avatar.positionControl.bindToVehicle(True)
        self.__curCtrl.enable()
        tmp = self.__curCtrl.getPreferredAutorotationMode()
        if tmp is not None:
            self.__isAutorotation = tmp
            self.__prevModeAutorotation = True
        else:
            self.__isAutorotation = True
            self.__prevModeAutorotation = None
        avatar.enableOwnVehicleAutorotation(self.__isAutorotation)
        self.__targeting.enable(True)
        self.__isStarted = True
        self.__isGUIVisible = True
        self.__killerVehicleID = None
        arena = avatar.arena
        arena.onPeriodChange += self.__onArenaStarted
        self.settingsCore.onSettingsChanged += self.__onSettingsChanged
        avatar.consistentMatrices.onVehicleMatrixBindingChanged += self.__onVehicleChanged
        self.__onArenaStarted(arena.period)
        if not avatar.isObserver() and arena.hasObservers:
            self.__remoteCameraSender = RemoteCameraSender(self)
        self.onCameraChanged('arcade')
        return

    def stop(self):
        self.__isStarted = False
        import SoundGroups
        SoundGroups.g_instance.changePlayMode(0)
        aih_global_binding.clear()
        for control in self.__ctrls.itervalues():
            control.destroy()

        replayCtrl = BattleReplay.g_replayCtrl
        if replayCtrl.isRecording:
            replayCtrl.setPlayerVehicleID(0)
        if self.__remoteCameraSender is not None:
            self.__remoteCameraSender.destroy()
            self.__remoteCameraSender = None
        self.onCameraChanged.clear()
        self.onCameraChanged = None
        self.onPostmortemVehicleChanged.clear()
        self.onPostmortemVehicleChanged = None
        self.onPostmortemKillerVisionEnter.clear()
        self.onPostmortemKillerVisionEnter = None
        self.onPostmortemKillerVisionExit.clear()
        self.onPostmortemKillerVisionExit = None
        self.__targeting.enable(False)
        self.__killerVehicleID = None
        if self.__onRecreateDevice in g_guiResetters:
            g_guiResetters.remove(self.__onRecreateDevice)
        BigWorld.player().arena.onPeriodChange -= self.__onArenaStarted
        self.settingsCore.onSettingsChanged -= self.__onSettingsChanged
        BigWorld.player().consistentMatrices.onVehicleMatrixBindingChanged -= self.__onVehicleChanged
        ComponentSystem.destroy(self)
        CallbackDelayer.destroy(self)
        return

    def __onVehicleChanged(self, isStatic):
        self.steadyVehicleMatrixCalculator.relinkSources()
        self.__identifySPG()
        if self.__waitObserverCallback is not None and self.__observerVehicle is not None:
            player = BigWorld.player()
            ownVehicle = BigWorld.entity(player.playerVehicleID)
            vehicle = player.getVehicleAttached()
            if vehicle != ownVehicle:
                self.__waitObserverCallback()
                self.__observerIsSwitching = False
                self.__observerVehicle = None
        return

    def setObservedVehicle(self, vehicleID):
        for control in self.__ctrls.itervalues():
            control.setObservedVehicle(vehicleID)

    def onControlModeChanged(self, eMode, **args):
        if self.steadyVehicleMatrixCalculator is not None:
            self.steadyVehicleMatrixCalculator.relinkSources()
        if not self.__isArenaStarted and eMode != _CTRL_MODE.POSTMORTEM:
            return
        else:
            player = BigWorld.player()
            isObserverMode = 'observer' in player.vehicleTypeDescriptor.type.tags if player is not None else True
            if self.__waitObserverCallback is not None:
                self.__waitObserverCallback = None
            if isObserverMode and eMode == _CTRL_MODE.POSTMORTEM:
                if self.__observerVehicle is not None and not self.__observerIsSwitching:
                    self.__waitObserverCallback = partial(self.onControlModeChanged, eMode, **args)
                    self.__observerIsSwitching = True
                    player.positionControl.followCamera(False)
                    player.positionControl.bindToVehicle(True, self.__observerVehicle)
                    return
            if isObserverMode and self.__ctrlModeName == _CTRL_MODE.POSTMORTEM:
                player = BigWorld.player()
                self.__observerVehicle = player.vehicle.id if player.vehicle else None
                self.__observerIsSwitching = False
            replayCtrl = BattleReplay.g_replayCtrl
            if replayCtrl.isRecording:
                replayCtrl.setControlMode(eMode)
            self.__curCtrl.disable()
            prevCtrl = self.__curCtrl
            self.__curCtrl = self.__ctrls[eMode]
            self.__ctrlModeName = eMode
            if player is not None:
                if not prevCtrl.isManualBind() and self.__curCtrl.isManualBind():
                    if isObserverMode:
                        player.positionControl.bindToVehicle(False, -1)
                    else:
                        player.positionControl.bindToVehicle(False)
                elif prevCtrl.isManualBind() and not self.__curCtrl.isManualBind():
                    if isObserverMode:
                        player.positionControl.followCamera(False)
                        player.positionControl.bindToVehicle(True, self.__observerVehicle)
                    else:
                        player.positionControl.bindToVehicle(True)
                elif not prevCtrl.isManualBind() and not self.__curCtrl.isManualBind():
                    if isObserverMode and not self.isObserverFPV:
                        player.positionControl.bindToVehicle(True)
                newAutoRotationMode = self.__curCtrl.getPreferredAutorotationMode()
                if newAutoRotationMode is not None:
                    if prevCtrl.getPreferredAutorotationMode() is None:
                        self.__prevModeAutorotation = self.__isAutorotation
                    if self.__isAutorotation != newAutoRotationMode:
                        self.__isAutorotation = newAutoRotationMode
                        BigWorld.player().enableOwnVehicleAutorotation(self.__isAutorotation)
                elif prevCtrl.getPreferredAutorotationMode() is not None:
                    if self.__prevModeAutorotation is None:
                        self.__prevModeAutorotation = True
                    if self.__isAutorotation != self.__prevModeAutorotation:
                        self.__isAutorotation = self.__prevModeAutorotation
                        BigWorld.player().enableOwnVehicleAutorotation(self.__isAutorotation)
                    self.__prevModeAutorotation = None
                if not isObserverMode and self.__ctrlModeName in (_CTRL_MODE.ARCADE, _CTRL_MODE.SNIPER):
                    lockEnabled = prevCtrl.getAimingMode(AIMING_MODE.TARGET_LOCK)
                    self.__curCtrl.setAimingMode(lockEnabled, AIMING_MODE.TARGET_LOCK)
            self.__targeting.onRecreateDevice()
            self.__curCtrl.setGUIVisible(self.__isGUIVisible)
            if isObserverMode:
                args.update(vehicleID=self.__observerVehicle)
                self.__curCtrl.enable(**args)
            else:
                self.__curCtrl.enable(**args)
            isReplayPlaying = replayCtrl.isPlaying
            vehicleID = None
            vehicle = player.getVehicleAttached()
            if isObserverMode:
                vehicleID = self.__observerVehicle
            elif vehicle is not None and isReplayPlaying:
                vehicleID = vehicle.id
            self.onCameraChanged(eMode, vehicleID)
            if not isReplayPlaying:
                self.__curCtrl.handleMouseEvent(0.0, 0.0, 0.0)
            return

    def onVehicleControlModeChanged(self, eMode):
        LOG_DEBUG('onVehicleControlModeChanged: ', eMode, self.isObserverFPV)
        if not self.isObserverFPV:
            self.onControlModeChanged(_CTRL_MODE.POSTMORTEM)
            return
        else:
            if eMode is None:
                eMode = _CTRL_MODES[BigWorld.player().observerFPVControlMode]
            targetPos = self.getDesiredShotPoint() or Math.Vector3(0, 0, 0)
            LOG_DEBUG('onVehicleControlModeChanged: ', eMode, targetPos)
            self.onControlModeChanged(eMode, preferredPos=targetPos, aimingMode=0, saveZoom=False, saveDist=True, equipmentID=None, curVehicleID=BigWorld.player().getVehicleAttached())
            return

    def getTargeting(self):
        return self.__targeting

    def setGUIVisible(self, isVisible):
        self.__isGUIVisible = isVisible
        self.__curCtrl.setGUIVisible(isVisible)

    def selectPlayer(self, vehId):
        self.__curCtrl.selectPlayer(vehId)

    def onMinimapClicked(self, worldPos):
        self.__curCtrl.onMinimapClicked(worldPos)

    def onVehicleShaken(self, vehicle, impulsePosition, impulseDir, caliber, shakeReason):
        if shakeReason == _ShakeReason.OWN_SHOT_DELAYED:
            shakeFuncBound = functools.partial(self.onVehicleShaken, vehicle, impulsePosition, impulseDir, caliber, _ShakeReason.OWN_SHOT)
            delayTime = self.__dynamicCameraSettings.settings['ownShotImpulseDelay']
            self.delayCallback(delayTime, shakeFuncBound)
            return
        else:
            camera = getattr(self.ctrl, 'camera', None)
            if camera is None:
                return
            impulseValue = self.__dynamicCameraSettings.getGunImpulse(caliber)
            vehicleSensitivity = 0.0
            avatarVehicle = BigWorld.player().getVehicleAttached()
            if avatarVehicle is None or not avatarVehicle.isAlive():
                return
            avatarVehicleTypeDesc = getattr(avatarVehicle, 'typeDescriptor', None)
            if avatarVehicleTypeDesc is not None:
                avatarVehWeightTons = avatarVehicleTypeDesc.physics['weight'] / 1000.0
                vehicleSensitivity = self.__dynamicCameraSettings.getSensitivityToImpulse(avatarVehWeightTons)
                vehicleSensitivity *= avatarVehicleTypeDesc.hull.swinging.sensitivityToImpulse
            impulseReason = None
            isDistant = False
            if shakeReason == _ShakeReason.OWN_SHOT:
                if vehicle is avatarVehicle:
                    impulseReason = cameras.ImpulseReason.MY_SHOT
                    isDistant = False
                else:
                    impulseReason = cameras.ImpulseReason.OTHER_SHOT
                    isDistant = True
            elif vehicle is avatarVehicle:
                if shakeReason == _ShakeReason.HIT or shakeReason == _ShakeReason.HIT_NO_DAMAGE:
                    impulseValue *= 1.0 if shakeReason == _ShakeReason.HIT else self.__dynamicCameraSettings.settings['zeroDamageHitSensitivity']
                    impulseReason = cameras.ImpulseReason.ME_HIT
                    isDistant = False
                else:
                    impulseReason = cameras.ImpulseReason.SPLASH
                    isDistant = True
            impulseDir, impulseValue = self.__adjustImpulse(impulseDir, impulseValue, camera, impulsePosition, vehicleSensitivity, impulseReason)
            if isDistant:
                camera.applyDistantImpulse(impulsePosition, impulseValue, impulseReason)
            else:
                camera.applyImpulse(impulsePosition, impulseDir * impulseValue, impulseReason)
            return

    def onVehicleCollision(self, vehicle, impactVelocity):
        if impactVelocity < self.__dynamicCameraSettings.settings['minCollisionSpeed']:
            return
        else:
            camera = getattr(self.ctrl, 'camera', None)
            if camera is None:
                return
            avatarVehicle = BigWorld.player().getVehicleAttached()
            if avatarVehicle is None or not avatarVehicle.isAlive():
                return
            if vehicle is avatarVehicle:
                impulse = Math.Vector3(0, impactVelocity * self.__dynamicCameraSettings.settings['collisionSpeedToImpulseRatio'], 0)
                camera.applyImpulse(vehicle.position, impulse, cameras.ImpulseReason.COLLISION)
            return

    def onVehicleDeath(self, vehicle, exploded):
        if not exploded:
            return
        else:
            camera = getattr(self.ctrl, 'camera', None)
            if camera is None:
                return
            avatarVehicle = BigWorld.player().getVehicleAttached()
            if avatarVehicle is None or avatarVehicle is vehicle:
                return
            caliber = vehicle.typeDescriptor.shot.shell.caliber
            impulseValue = self.__dynamicCameraSettings.getGunImpulse(caliber)
            avatarVehicleWeightInTons = avatarVehicle.typeDescriptor.physics['weight'] / 1000.0
            vehicleSensitivity = self.__dynamicCameraSettings.getSensitivityToImpulse(avatarVehicleWeightInTons)
            vehicleSensitivity *= avatarVehicle.typeDescriptor.hull.swinging.sensitivityToImpulse
            _, impulseValue = self.__adjustImpulse(Math.Vector3(0, 0, 0), impulseValue, camera, vehicle.position, vehicleSensitivity, cameras.ImpulseReason.VEHICLE_EXPLOSION)
            camera.applyDistantImpulse(vehicle.position, impulseValue, cameras.ImpulseReason.VEHICLE_EXPLOSION)
            return

    def onExplosionImpulse(self, position, impulseValue):
        camera = getattr(self.ctrl, 'camera', None)
        if camera is None:
            return
        else:
            avatarVehicle = BigWorld.player().getVehicleAttached()
            if avatarVehicle is None:
                return
            avatarVehicleWeightInTons = avatarVehicle.typeDescriptor.physics['weight'] / 1000.0
            vehicleSensitivity = self.__dynamicCameraSettings.getSensitivityToImpulse(avatarVehicleWeightInTons)
            vehicleSensitivity *= avatarVehicle.typeDescriptor.hull.swinging.sensitivityToImpulse
            _, impulseValue = self.__adjustImpulse(Math.Vector3(0, 0, 0), impulseValue, camera, position, vehicleSensitivity, cameras.ImpulseReason.HE_EXPLOSION)
            camera.applyDistantImpulse(position, impulseValue, cameras.ImpulseReason.HE_EXPLOSION)
            return

    def onProjectileHit(self, position, caliber, isOwnShot):
        if not isOwnShot:
            return
        else:
            camera = getattr(self.ctrl, 'camera', None)
            if camera is None:
                return
            impulseValue = self.__dynamicCameraSettings.getGunImpulse(caliber)
            vehicleSensitivity = 1.0
            avatarVehicle = BigWorld.player().getVehicleAttached()
            if avatarVehicle is not None:
                avatarVehicleWeightInTons = avatarVehicle.typeDescriptor.physics['weight'] / 1000.0
                vehicleSensitivity = self.__dynamicCameraSettings.getSensitivityToImpulse(avatarVehicleWeightInTons)
                vehicleSensitivity *= avatarVehicle.typeDescriptor.hull.swinging.sensitivityToImpulse
            _, impulseValue = self.__adjustImpulse(Math.Vector3(0, 0, 0), impulseValue, camera, position, vehicleSensitivity, cameras.ImpulseReason.VEHICLE_EXPLOSION)
            camera.applyDistantImpulse(position, impulseValue, cameras.ImpulseReason.PROJECTILE_HIT)
            return

    def onSpecificImpulse(self, position, impulse, specificCtrl=None):
        if specificCtrl is None:
            camera = getattr(self.ctrl, 'camera', None)
        else:
            camera = self.ctrls[specificCtrl].camera
        if camera is None:
            return
        else:
            camera.applyImpulse(position, impulse, cameras.ImpulseReason.MY_SHOT)
            return

    def __adjustImpulse(self, impulseDir, impulseValue, camera, impulsePosition, vehicleSensitivity, impulseReason):
        if impulseReason in camera.getReasonsAffectCameraDirectly():
            dirToCamera = camera.camera.position - impulsePosition
            dirToCamera.normalise()
            impulseDir = dirToCamera
        else:
            impulseValue *= vehicleSensitivity
        return (impulseDir, impulseValue)

    def __identifySPG(self):
        veh = BigWorld.entity(BigWorld.player().playerVehicleID)
        if veh is None:
            return
        else:
            vehTypeDesc = veh.typeDescriptor.type
            self.__isSPG = 'SPG' in vehTypeDesc.tags
            self.__isATSPG = 'AT-SPG' in vehTypeDesc.tags
            return

    def reloadDynamicSettings(self):
        if not constants.HAS_DEV_RESOURCES:
            return
        ResMgr.purge(INPUT_HANDLER_CFG)
        sec = ResMgr.openSection(INPUT_HANDLER_CFG)
        self.__dynamicCameraSettings = DynamicCameraSettings(sec['dynamicCameraCommon'])
        try:
            self.__ctrls['sniper'].camera.aimingSystem.reloadConfig(sec['sniperMode']['camera'])
        except Exception:
            pass

    def _readCfg(self):
        sec = ResMgr.openSection(INPUT_HANDLER_CFG)
        if sec is None:
            LOG_ERROR('can not open <%s>.' % INPUT_HANDLER_CFG)
            return
        else:
            self.__checkSections(sec)
            keySec = sec['keys']
            if keySec is not None:
                self.__showMarkersKey = getattr(Keys, keySec.readString('showMarkersKey', ''), None)
                self.__alwaysShowAimKey = getattr(Keys, keySec.readString('alwaysShowAimKey', ''), None)
            self.__dynamicCameraSettings = DynamicCameraSettings(sec['dynamicCameraCommon'])
            return sec

    def __setupCtrls(self, section):
        modules = (control_modes,
         MapCaseMode,
         RespawnDeathMode,
         epic_battle_death_mode)
        bonusType = BigWorld.player().arenaBonusType
        bonusTypeCtrlsMap = _OVERWRITE_CTRLS_DESC_MAP.get(bonusType, {})
        for name, desc in _CTRLS_DESC_MAP.items():
            if bonusTypeCtrlsMap.has_key(name):
                desc = bonusTypeCtrlsMap.get(name)
            try:
                if desc[2] != _CTRL_TYPE.DEVELOPMENT or desc[2] == _CTRL_TYPE.DEVELOPMENT and constants.HAS_DEV_RESOURCES:
                    if name not in self.__ctrls:
                        for module in modules:
                            classType = getattr(module, desc[0], None)
                            if classType is None:
                                pass
                            self.__ctrls[name] = classType(section[desc[1]] if desc[1] else None, self)
                            break

            except Exception:
                LOG_DEBUG('Error while setting ctrls', name, desc, constants.HAS_DEV_RESOURCES)
                LOG_CURRENT_EXCEPTION()

        return

    def __checkSections(self, section):
        for _, desc in _CTRLS_DESC_MAP.items():
            if desc[1] is None or desc[2] == _CTRL_TYPE.OPTIONAL or desc[2] == _CTRL_TYPE.DEVELOPMENT and not constants.HAS_DEV_RESOURCES:
                continue
            if not section.has_key(desc[1]):
                LOG_ERROR('Invalid section <%s> in <%s>.' % (desc[1], INPUT_HANDLER_CFG))

        return

    def __onArenaStarted(self, period, *args):
        self.__isArenaStarted = period == ARENA_PERIOD.BATTLE
        self.__curCtrl.setGunMarkerFlag(self.__isArenaStarted, _GUN_MARKER_FLAG.CONTROL_ENABLED)
        self.showGunMarker2(gun_marker_ctrl.useServerGunMarker())
        self.showGunMarker(gun_marker_ctrl.useClientGunMarker())

    def __onRecreateDevice(self):
        self.__curCtrl.onRecreateDevice()
        self.__targeting.onRecreateDevice()

    def __onSettingsChanged(self, diff):
        if 'dynamicCamera' in diff or 'horStabilizationSnp' in diff:
            dynamicCamera = self.settingsCore.getSetting('dynamicCamera')
            horStabilizationSnp = self.settingsCore.getSetting('horStabilizationSnp')
            self.enableDynamicCamera(dynamicCamera, horStabilizationSnp)

    def __onRequestFail(self):
        player = BigWorld.player()
        if player is not None:
            player.showVehicleError('cantSwitchEngineDestroyed')
        return
예제 #2
0
class CompoundAppearance(ComponentSystem, CallbackDelayer):
    compoundModel = property(lambda self: self.__compoundModel)
    boundEffects = property(lambda self: self.__boundEffects)
    fashion = property(lambda self: self.__fashions.chassis)
    typeDescriptor = property(lambda self: self.__typeDesc)
    id = property(lambda self: self.__vID)
    isAlive = property(lambda self: self.__isAlive)
    isObserver = property(lambda self: self.__isObserver)

    def __setFashions(self, fashions, isTurretDetached = False):
        self.__fashions = fashions
        self.__fashion = fashions.chassis
        if isTurretDetached:
            self.__compoundModel.setupFashions((fashions.chassis, fashions.hull))
        else:
            self.__compoundModel.setupFashions(fashions)

    fashions = property(lambda self: self.__fashions, __setFashions)
    terrainMatKind = property(lambda self: self.__currTerrainMatKind)
    terrainEffectMaterialNames = property(lambda self: self.__terrainEffectMaterialNames)
    isInWater = property(lambda self: self.waterSensor.isInWater)
    isUnderwater = property(lambda self: self.waterSensor.isUnderWater)
    waterHeight = property(lambda self: self.waterSensor.waterHeight)
    damageState = property(lambda self: self.__currentDamageState)
    frameTimeStamp = 0
    rightTrackScroll = property(lambda self: self.__rightTrackScroll)
    leftTrackScroll = property(lambda self: self.__leftTrackScroll)
    splineTracks = property(lambda self: self.__splineTracks)
    activated = property(lambda self: self.__activated)
    isFlying = property(lambda self: self.flyingInfoProvider is not None and self.flyingInfoProvider.isFlying)
    isLeftSideFlying = property(lambda self: self.flyingInfoProvider is not None and self.flyingInfoProvider.isLeftSideFlying)
    isRightSideFlying = property(lambda self: self.flyingInfoProvider is not None and self.flyingInfoProvider.isRightSideFlying)

    @property
    def rpm(self):
        if self.detailedEngineState is not None:
            return self.detailedEngineState.rpm
        else:
            return 0.0

    @property
    def gear(self):
        if self.detailedEngineState is not None:
            return self.detailedEngineState.gearNum
        else:
            return 0.0

    trackScrollController = property(lambda self: self.__trackScrollCtl)
    filter = AutoProperty()
    detailedEngineState = ComponentDescriptor()
    engineAudition = ComponentDescriptor()
    trackCrashAudition = ComponentDescriptor()
    customEffectManager = ComponentDescriptor()
    highlighter = ComponentDescriptor()
    gunRecoil = ComponentDescriptor()
    gunLinkedNodesAnimator = ComponentDescriptor()
    swingingAnimator = ComponentDescriptor()
    suspensionSound = ComponentDescriptor()
    siegeEffects = ComponentDescriptor()
    lodCalculator = ComponentDescriptor()
    frictionAudition = ComponentDescriptor()
    suspension = ComponentDescriptor()
    leveredSuspension = ComponentDescriptor()
    suspensionController = ComponentDescriptor()
    wheelsAnimator = ComponentDescriptor()
    trackNodesAnimator = ComponentDescriptor()
    vehicleTraces = ComponentDescriptor()
    flyingInfoProvider = ComponentDescriptor()
    terrainMatKindSensor = ComponentDescriptor()
    waterSensor = ComponentDescriptor()
    peripheralsController = ComponentDescriptor()

    def __init__(self):
        CallbackDelayer.__init__(self)
        ComponentSystem.__init__(self)
        self.turretMatrix = Math.WGAdaptiveMatrixProvider()
        self.gunMatrix = Math.WGAdaptiveMatrixProvider()
        self.__vehicle = None
        self.__filter = None
        self.__originalFilter = None
        self.__typeDesc = None
        self.__fashion = None
        self.__crashedTracksCtrl = None
        self.__currentDamageState = VehicleDamageState()
        self.__effectsPlayer = None
        self.__engineMode = (0, 0)
        self.__currTerrainMatKind = [-1] * _MATKIND_COUNT
        self.__terrainEffectMaterialNames = [''] * _MATKIND_COUNT
        self.__chassisDecal = VehicleDecal(self)
        self.__splodge = None
        self.__vehicleStickers = None
        self.onModelChanged = Event()
        self.__leftTrackScroll = 0.0
        self.__rightTrackScroll = 0.0
        self.__fashions = None
        self.__compoundModel = None
        self.__boundEffects = None
        self.__splineTracks = None
        self.flyingInfoProvider = Vehicular.FlyingInfoProvider()
        self.__trackScrollCtl = BigWorld.PyTrackScroll()
        self.__trackScrollCtl.setFlyingInfo(DataLinks.createBoolLink(self.flyingInfoProvider, 'isLeftSideFlying'), DataLinks.createBoolLink(self.flyingInfoProvider, 'isRightSideFlying'))
        self.__weaponEnergy = 0.0
        self.__activated = False
        self.__systemStarted = False
        self.__vID = 0
        self.__isAlive = True
        self.__isTurretDetached = False
        self.__periodicTimerID = None
        self.__wasDeactivated = False
        self.__inSpeedTreeCollision = False
        self.__isObserver = False
        return

    def prerequisites(self, typeDescriptor, vID, health, isCrewActive, isTurretDetached, outfitCD):
        self.__currentDamageState.update(health, isCrewActive, False)
        outfit = Outfit(outfitCD)
        out = camouflages.getCamoPrereqs(outfit, typeDescriptor)
        splineDesc = typeDescriptor.chassis.splineDesc
        if splineDesc is not None:
            out.append(splineDesc.segmentModelLeft)
            out.append(splineDesc.segmentModelRight)
            if splineDesc.segment2ModelLeft is not None:
                out.append(splineDesc.segment2ModelLeft)
            if splineDesc.segment2ModelRight is not None:
                out.append(splineDesc.segment2ModelRight)
        self.__vID = vID
        self.__typeDesc = typeDescriptor
        self.__isTurretDetached = isTurretDetached
        return out

    def setVehicle(self, vehicle):
        self.__vehicle = vehicle
        if self.customEffectManager is not None:
            self.customEffectManager.setVehicle(vehicle)
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.setVehicle(vehicle)
        if self.frictionAudition is not None:
            self.frictionAudition.setVehicleMatrix(vehicle.matrix)
        self.highlighter.setVehicle(vehicle)
        self.__applyVehicleOutfit()
        return

    def __arenaPeriodChanged(self, period, *otherArgs):
        if self.detailedEngineState is None:
            return
        else:
            periodEndTime = BigWorld.player().arena.periodEndTime
            serverTime = BigWorld.serverTime()
            engine_state.notifyEngineOnArenaPeriodChange(self.detailedEngineState, period, periodEndTime, serverTime)
            return

    def activate(self):
        if self.__activated or self.__vehicle is None:
            return
        else:
            super(CompoundAppearance, self).activate()
            isPlayerVehicle = self.__vehicle.isPlayerVehicle
            self.__isObserver = 'observer' in self.__typeDesc.type.tags
            player = BigWorld.player()
            self.__originalFilter = self.__vehicle.filter
            self.__vehicle.filter = self.__filter
            self.__vehicle.filter.enableStabilisedMatrix(isPlayerVehicle)
            self.__filter.isStrafing = self.__vehicle.isStrafing
            self.__filter.vehicleCollisionCallback = player.handleVehicleCollidedVehicle
            self.__compoundModel.isHighPriorityReflection = isPlayerVehicle
            if isPlayerVehicle:
                if player.inputHandler is not None:
                    player.inputHandler.addVehicleToCameraCollider(self.__vehicle)
                self.__inSpeedTreeCollision = True
                BigWorld.setSpeedTreeCollisionBody(self.__compoundModel.getBoundsForPart(TankPartIndexes.HULL))
            self.__linkCompound()
            if not self.__isObserver:
                self.__chassisDecal.attach()
            self.__createAndAttachStickers()
            self.__startSystems()
            self.setupGunMatrixTargets()
            if not self.__isObserver:
                self.__vehicle.filter.enableLagDetection(not self.__currentDamageState.isCurrentModelDamaged)
            self.onModelChanged()
            if self.lodCalculator is not None:
                self.lodCalculator.setupPosition(DataLinks.linkMatrixTranslation(self.__compoundModel.matrix))
            if hasattr(self.filter, 'placingCompensationMatrix') and self.swingingAnimator is not None:
                self.swingingAnimator.placingCompensationMatrix = self.filter.placingCompensationMatrix
                self.swingingAnimator.worldMatrix = self.__compoundModel.matrix
            if self.__periodicTimerID is not None:
                BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = BigWorld.callback(_PERIODIC_TIME, self.__onPeriodicTimer)
            if self.fashion is not None:
                self.fashion.activate()
            if self.__isObserver:
                self.__compoundModel.visible = False
            BigWorld.player().arena.onPeriodChange += self.__arenaPeriodChanged
            BigWorld.player().inputHandler.onCameraChanged += self.__onCameraChanged
            if self.detailedEngineState is not None:
                engine_state.checkEngineStart(self.detailedEngineState, BigWorld.player().arena.period)
            self.__activated = True
            return

    def deactivate(self, stopEffects = True):
        if not self.__activated:
            return
        else:
            self.__activated = False
            self.__wasDeactivated = True
            if self.fashion is not None:
                self.fashion.deactivate()
            self.__stopSystems()
            super(CompoundAppearance, self).deactivate()
            self.__chassisDecal.detach()
            if self.__inSpeedTreeCollision:
                BigWorld.setSpeedTreeCollisionBody(None)
            BigWorld.player().inputHandler.removeVehicleFromCameraCollider(self.__vehicle)
            self.__vehicle.filter.enableLagDetection(False)
            self.turretMatrix.target = None
            self.gunMatrix.target = None
            self.__vehicle.filter = self.__originalFilter
            self.__filter.reset()
            self.__originalFilter = None
            self.__vehicleStickers.detach()
            if stopEffects:
                self.__stopEffects()
                self.__boundEffects.stop()
            self.__vehicle.model = None
            self.__compoundModel.matrix = Math.Matrix()
            self.__vehicle = None
            if self.__crashedTracksCtrl is not None:
                self.__crashedTracksCtrl.deactivate()
            BigWorld.player().arena.onPeriodChange -= self.__arenaPeriodChanged
            BigWorld.player().inputHandler.onCameraChanged -= self.__onCameraChanged
            return

    def __startSystems(self):
        if self.__systemStarted or self.__currentDamageState.isCurrentModelDamaged:
            return
        else:
            if self.flyingInfoProvider is not None:
                self.flyingInfoProvider.setData(self.__vehicle.filter, self.suspension)
            if self.__trackScrollCtl is not None:
                self.__trackScrollCtl.activate()
                self.__trackScrollCtl.setData(self.__vehicle.filter)
            if self.__vehicle.isPlayerVehicle:
                self.delayCallback(_PERIODIC_TIME_ENGINE, self.__onPeriodicTimerEngine)
                self.highlighter.highlight(True)
            if self.engineAudition is not None:
                self.engineAudition.setWeaponEnergy(self.__weaponEnergy)
                self.engineAudition.attachToModel(self.__compoundModel)
            if self.peripheralsController is not None:
                self.peripheralsController.attachToVehicle(self.__vehicle)
            if self.suspensionController is not None:
                self.suspensionController.setData(self.__vehicle.filter, self.__vehicle.typeDescriptor)
            if self.detailedEngineState is not None:
                self.detailedEngineState.onGearUpCbk = self.__onEngineStateGearUp
            self.__systemStarted = True
            return

    def __stopSystems(self):
        if not self.__systemStarted:
            return
        else:
            self.__systemStarted = False
            if self.__periodicTimerID is not None:
                BigWorld.cancelCallback(self.__periodicTimerID)
                self.__periodicTimerID = None
            if self.flyingInfoProvider is not None:
                self.flyingInfoProvider.setData(None, None)
            if self.__vehicle.isPlayerVehicle:
                self.highlighter.highlight(False)
                self.stopCallback(self.__onPeriodicTimerEngine)
            if self.__trackScrollCtl is not None:
                self.__trackScrollCtl.deactivate()
                self.__trackScrollCtl.setData(None)
            if self.detailedEngineState is not None:
                self.detailedEngineState.onGearUpCbk = None
            return

    def __destroySystems(self):
        if self.__periodicTimerID is not None:
            BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = None
        if self.__trackScrollCtl is not None:
            self.__trackScrollCtl.deactivate()
            self.__trackScrollCtl = None
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.destroy()
            self.__crashedTracksCtrl = None
        self.__systemStarted = False
        return

    def __destroyEngineAudition(self):
        self.engineAudition = None
        if self.detailedEngineState is not None:
            self.detailedEngineState.onEngineStart = None
            self.detailedEngineState.onStateChanged = None
        return

    def __prepareSystemsForDamagedVehicle(self, vehicle, isTurretDetached):
        if self.flyingInfoProvider is not None:
            self.flyingInfoProvider.setData(vehicle.filter, None)
        self.vehicleTraces = None
        self.suspensionSound = None
        self.swingingAnimator = None
        self.gunRecoil = None
        self.gunLinkedNodesAnimator = None
        self.suspension = None
        self.leveredSuspension = None
        self.trackNodesAnimator = None
        self.wheelsAnimator = None
        fashions = VehiclePartsTuple(None, None, None, None)
        self.__setFashions(fashions, isTurretDetached)
        self.customEffectManager = None
        self.__destroyEngineAudition()
        self.detailedEngineState = None
        self.trackCrashAudition = None
        self.frictionAudition = None
        self.terrainMatKindSensor = None
        self.__splineTracks = None
        model = self.compoundModel
        self.waterSensor.sensorPlaneLink = model.root
        self.peripheralsController = None
        self.__destroySystems()
        return

    def destroy(self):
        if self.__vehicle is not None:
            self.deactivate()
        self.__destroySystems()
        ComponentSystem.destroy(self)
        self.__typeDesc = None
        if self.__boundEffects is not None:
            self.__boundEffects.destroy()
        self.__vehicleStickers = None
        self.onModelChanged = None
        self.__chassisDecal.destroy()
        self.__chassisDecal = None
        self.__compoundModel = None
        CallbackDelayer.destroy(self)
        return

    def start(self, prereqs = None):
        for hitTester in self.__typeDesc.getHitTesters():
            hitTester.loadBspModel()

        self.__compoundModel = prereqs[self.__typeDesc.name]
        self.__boundEffects = bound_effects.ModelBoundEffects(self.__compoundModel)
        isCurrentModelDamaged = self.__currentDamageState.isCurrentModelDamaged
        fashions = camouflages.prepareFashions(isCurrentModelDamaged)
        if not isCurrentModelDamaged:
            model_assembler.setupVehicleFashion(fashions.chassis, self.__typeDesc)
        self.__setFashions(fashions, self.__isTurretDetached)
        self.__setupModels()
        if not isCurrentModelDamaged:
            self.__splineTracks = model_assembler.setupSplineTracks(self.__fashion, self.__typeDesc, self.__compoundModel, prereqs)
            self.__crashedTracksCtrl = CrashedTrackController(self.__typeDesc, self.__fashion)
        else:
            self.__trackScrollCtl = None
        if self.__currentDamageState.effect is not None:
            self.__playEffect(self.__currentDamageState.effect, SpecialKeyPointNames.STATIC)
        self.__chassisDecal.create()
        return

    def showStickers(self, show):
        self.__vehicleStickers.show = show

    def updateTurretVisibility(self):
        self.__requestModelsRefresh()

    def changeVisibility(self, modelVisible):
        self.compoundModel.visible = modelVisible
        self.showStickers(modelVisible)
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.setVisible(modelVisible)
        return

    def changeDrawPassVisibility(self, visibilityMask):
        colorPassEnabled = visibilityMask & BigWorld.ColorPassBit != 0
        self.compoundModel.visible = visibilityMask
        self.compoundModel.skipColorPass = not colorPassEnabled
        self.showStickers(colorPassEnabled)
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.setVisible(visibilityMask)
        return

    def onVehicleHealthChanged(self, showEffects = True):
        vehicle = self.__vehicle
        if not vehicle.isAlive() and vehicle.health > 0:
            self.changeEngineMode((0, 0))
        currentState = self.__currentDamageState
        previousState = currentState.state
        isUnderWater = self.waterSensor.isUnderWater
        currentState.update(vehicle.health, vehicle.isCrewActive, isUnderWater)
        if previousState != currentState.state:
            if currentState.effect is not None and showEffects:
                self.__playEffect(currentState.effect)
            if vehicle.health <= 0:
                BigWorld.player().inputHandler.onVehicleDeath(vehicle, currentState.state == 'ammoBayExplosion')
                self.processVehicleDeath(currentState)
                self.__requestModelsRefresh()
            elif not vehicle.isCrewActive:
                self.__onCrewKilled()
        return

    @ComponentSystem.groupCall
    def processVehicleDeath(self, vehicleDamageState):
        pass

    def showAmmoBayEffect(self, mode, fireballVolume):
        if mode == constants.AMMOBAY_DESTRUCTION_MODE.POWDER_BURN_OFF:
            self.__playEffect('ammoBayBurnOff')
            return
        volumes = items.vehicles.g_cache.commonConfig['miscParams']['explosionCandleVolumes']
        candleIdx = 0
        for idx, volume in enumerate(volumes):
            if volume >= fireballVolume:
                break
            candleIdx = idx + 1

        if candleIdx > 0:
            self.__playEffect('explosionCandle%d' % candleIdx)
        else:
            self.__playEffect('explosion')

    def changeEngineMode(self, mode, forceSwinging = False):
        self.__engineMode = mode
        if self.detailedEngineState is not None:
            self.detailedEngineState.mode = self.__engineMode[0]
        if self.__trackScrollCtl is not None:
            self.__trackScrollCtl.setMode(self.__engineMode)
        if BattleReplay.isPlaying() and BattleReplay.g_replayCtrl.isTimeWarpInProgress:
            return
        else:
            return

    def stopSwinging(self):
        if self.swingingAnimator is not None:
            self.swingingAnimator.accelSwingingPeriod = 0.0
        return

    def removeDamageSticker(self, code):
        self.__vehicleStickers.delDamageSticker(code)

    def addDamageSticker(self, code, componentName, stickerID, segStart, segEnd):
        self.__vehicleStickers.addDamageSticker(code, componentName, stickerID, segStart, segEnd)

    def receiveShotImpulse(self, dir, impulse):
        if BattleReplay.isPlaying() and BattleReplay.g_replayCtrl.isTimeWarpInProgress:
            return
        else:
            if not VehicleDamageState.isDamagedModel(self.__currentDamageState.modelState):
                self.swingingAnimator.receiveShotImpulse(dir, impulse)
                if self.__crashedTracksCtrl is not None:
                    self.__crashedTracksCtrl.receiveShotImpulse(dir, impulse)
            return

    def recoil(self):
        gunNode = self.compoundModel.node(TankNodeNames.GUN_INCLINATION)
        impulseDir = Math.Matrix(gunNode).applyVector(Math.Vector3(0, 0, -1))
        impulseValue = self.__typeDesc.gun.impulse
        self.receiveShotImpulse(impulseDir, impulseValue)
        self.gunRecoil.recoil()
        node = self.compoundModel.node('HP_gunFire')
        gunPos = Math.Matrix(node).translation
        BigWorld.player().inputHandler.onVehicleShaken(self.__vehicle, gunPos, impulseDir, self.__typeDesc.shot.shell.caliber, ShakeReason.OWN_SHOT_DELAYED)

    def addCrashedTrack(self, isLeft):
        if not self.__vehicle.isAlive():
            return
        else:
            if self.__crashedTracksCtrl is not None:
                self.__crashedTracksCtrl.addTrack(isLeft, self.isLeftSideFlying if isLeft else self.isRightSideFlying)
            if not self.__vehicle.isEnteringWorld and self.trackCrashAudition:
                self.trackCrashAudition.playCrashSound(isLeft)
            return

    def delCrashedTrack(self, isLeft):
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.delTrack(isLeft)
        if not self.__vehicle.isEnteringWorld and self.trackCrashAudition and self.__vehicle.isPlayerVehicle:
            self.trackCrashAudition.playCrashSound(isLeft, True)
        return

    def __getVehicleOutfit(self):
        if not self.__vehicle:
            return Outfit()
        outfitCD = self.__vehicle.publicInfo['outfit']
        outfit = Outfit(outfitCD)
        if not (self.__vehicle.isPlayerVehicle or outfit.isHistorical()):
            if BigWorld.player().isC11nHistorical:
                outfit = Outfit()
        return outfit

    def __applyVehicleOutfit(self):
        outfit = self.__getVehicleOutfit()
        camouflages.updateFashions(self.__fashions, self.__typeDesc, self.__currentDamageState.isCurrentModelDamaged, outfit)

    def __requestModelsRefresh(self):
        currentModelState = self.__currentDamageState.modelState
        assembler = model_assembler.prepareCompoundAssembler(self.__typeDesc, currentModelState, self.__vehicle.spaceID, self.__vehicle.isTurretDetached)
        BigWorld.loadResourceListBG((assembler,), makeCallbackWeak(self.__onModelsRefresh, currentModelState))

    def __onModelsRefresh(self, modelState, resourceList):
        if not self.damageState.isCurrentModelDamaged:
            raise AssertionError
            return BattleReplay.isFinished() and None
        elif not modelState == self.__currentDamageState.modelState:
            raise AssertionError
            return self.__vehicle is None and None
        else:
            prevTurretYaw = Math.Matrix(self.turretMatrix).yaw
            prevGunPitch = Math.Matrix(self.gunMatrix).pitch
            vehicle = self.__vehicle
            newCompoundModel = resourceList[self.__typeDesc.name]
            self.deactivate(False)
            self.__compoundModel = newCompoundModel
            self.__isTurretDetached = vehicle.isTurretDetached
            self.__prepareSystemsForDamagedVehicle(vehicle, self.__isTurretDetached)
            self.__setupModels()
            self.setVehicle(vehicle)
            self.activate()
            self.__reattachComponents(self.__compoundModel, self.__weaponEnergy)
            self.__filter.syncGunAngles(prevTurretYaw, prevGunPitch)
            model_assembler.setupTurretRotations(self)
            return None

    def __setupModels(self):
        self.__isAlive = not self.__currentDamageState.isCurrentModelDamaged
        if self.__isAlive:
            _, gunLength = self.__computeVehicleHeight()
            self.__weaponEnergy = gunLength * self.__typeDesc.shot.shell.caliber
            self.__setupHavok()
        if MAX_DISTANCE > 0:
            transform = self.__typeDesc.chassis.AODecals[0]
            self.__attachSplodge(BigWorld.Splodge(transform, MAX_DISTANCE, self.__typeDesc.chassis.hullPosition.y))

    def __setupHavok(self):
        vDesc = self.__typeDesc
        node = self.compoundModel.node(TankPartNames.HULL)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.hull.hitTester.getBspModel())
        if hkm is not None:
            node.attach(hkm)
        node = self.compoundModel.node(TankPartNames.TURRET)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.turret.hitTester.getBspModel())
        if hkm is not None:
            node.attach(hkm)
        node = self.compoundModel.node(TankPartNames.CHASSIS)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.chassis.hitTester.getBspModel())
        if hkm is not None:
            node.attach(hkm)
        node = self.compoundModel.node(TankPartNames.GUN)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.gun.hitTester.getBspModel())
        if hkm is not None:
            node.attach(hkm)
        return

    def __reattachComponents(self, model, weaponEnergy):
        self.__boundEffects.reattachTo(model)
        if self.__effectsPlayer is not None:
            self.__effectsPlayer.reattachTo(model)
        if self.engineAudition is not None:
            self.engineAudition.setWeaponEnergy(weaponEnergy)
            self.engineAudition.attachToModel(model)
        return

    def __playEffect(self, kind, *modifs):
        self.__stopEffects()
        if kind == 'empty' or self.__vehicle is None:
            return
        else:
            enableDecal = True
            if kind in ('explosion', 'destruction') and self.isFlying:
                enableDecal = False
            if self.isUnderwater:
                if kind not in ('submersionDeath',):
                    return
            effects = self.typeDescriptor.type.effects[kind]
            if not effects:
                return
            vehicle = self.__vehicle
            effects = random.choice(effects)
            self.__effectsPlayer = EffectsListPlayer(effects[1], effects[0], showShockWave=vehicle.isPlayerVehicle, showFlashBang=vehicle.isPlayerVehicle, isPlayer=vehicle.isPlayerVehicle, showDecal=enableDecal, start=vehicle.position + Math.Vector3(0.0, -1.0, 0.0), end=vehicle.position + Math.Vector3(0.0, 1.0, 0.0), entity_id=vehicle.id)
            self.__effectsPlayer.play(self.__compoundModel, *modifs)
            return

    __SPORT_ACTIONS_CAMOUFLAGES = {'ussr:T62A_sport': (95, 94),
     'usa:M24_Chaffee_GT': (82, 83)}

    def __getCamouflageParams(self, vDesc, vID):
        vehicleInfo = BigWorld.player().arena.vehicles.get(vID)
        if vehicleInfo is not None:
            camouflageIdPerTeam = CompoundAppearance.__SPORT_ACTIONS_CAMOUFLAGES.get(vDesc.name)
            if camouflageIdPerTeam is not None:
                camouflageId = camouflageIdPerTeam[0] if vehicleInfo['team'] == 1 else camouflageIdPerTeam[1]
                return (camouflageId, time.time(), 100.0)
            camouflagePseudoname = vehicleInfo['events'].get('hunting', None)
            if camouflagePseudoname is not None:
                camouflIdsByNation = {0: {'black': 29,
                     'gold': 30,
                     'red': 31,
                     'silver': 32},
                 1: {'black': 25,
                     'gold': 26,
                     'red': 27,
                     'silver': 28},
                 2: {'black': 52,
                     'gold': 50,
                     'red': 51,
                     'silver': 53},
                 3: {'black': 48,
                     'gold': 46,
                     'red': 47,
                     'silver': 49},
                 4: {'black': 60,
                     'gold': 58,
                     'red': 59,
                     'silver': 61},
                 5: {'black': 56,
                     'gold': 54,
                     'red': 55,
                     'silver': 57},
                 6: {'black': 133,
                     'gold': 134,
                     'red': 135,
                     'silver': 136},
                 7: {'black': 141,
                     'gold': 142,
                     'red': 143,
                     'silver': 144},
                 8: {'black': 154,
                     'gold': 155,
                     'red': 156,
                     'silver': 157}}
                camouflIds = camouflIdsByNation.get(vDesc.type.customizationNationID)
                if camouflIds is not None:
                    ret = camouflIds.get(camouflagePseudoname)
                    if ret is not None:
                        return (ret, time.time(), 100.0)
        arenaType = BigWorld.player().arena.arenaType
        camouflageKind = arenaType.vehicleCamouflageKind
        return vDesc.camouflages[camouflageKind]

    def __stopEffects(self):
        if self.__effectsPlayer is not None:
            self.__effectsPlayer.stop()
        self.__effectsPlayer = None
        return

    def __onCrewKilled(self):
        self.__destroyEngineAudition()
        if self.customEffectManager is not None:
            self.customEffectManager = None
        return

    def onWaterSplash(self, waterHitPoint, isHeavySplash):
        effectName = 'waterCollisionHeavy' if isHeavySplash else 'waterCollisionLight'
        self.__vehicle.showCollisionEffect(waterHitPoint, effectName, Math.Vector3(0.0, 1.0, 0.0))

    def onUnderWaterSwitch(self, isUnderWater):
        if isUnderWater and self.__effectsPlayer is not None and self.__currentDamageState.effect not in ('submersionDeath',):
            self.__stopEffects()
        extra = self.__vehicle.typeDescriptor.extrasDict['fire']
        if extra.isRunningFor(self.__vehicle):
            extra.checkUnderwater(self.__vehicle, isUnderWater)
        return

    def updateTracksScroll(self, leftScroll, rightScroll):
        self.__leftTrackScroll = leftScroll
        self.__rightTrackScroll = rightScroll
        if self.__trackScrollCtl is not None:
            self.__trackScrollCtl.setExternal(leftScroll, rightScroll)
        return

    def __onPeriodicTimerEngine(self):
        if self.detailedEngineState is None or self.engineAudition is None:
            return
        else:
            if self.siegeEffects is not None:
                self.siegeEffects.tick(_PERIODIC_TIME_ENGINE)
            return _PERIODIC_TIME_ENGINE

    def __onPeriodicTimer(self):
        timeStamp = BigWorld.wg_getFrameTimestamp()
        if CompoundAppearance.frameTimeStamp >= timeStamp:
            self.__periodicTimerID = BigWorld.callback(0.0, self.__onPeriodicTimer)
            return
        else:
            CompoundAppearance.frameTimeStamp = timeStamp
            self.__periodicTimerID = BigWorld.callback(_PERIODIC_TIME, self.__onPeriodicTimer)
            if self.__vehicle is None:
                return
            vehicle = self.__vehicle
            if self.peripheralsController is not None:
                self.peripheralsController.update(vehicle, self.__crashedTracksCtrl)
            if not vehicle.isAlive():
                return
            distanceFromPlayer = (BigWorld.camera().position - vehicle.position).length
            self.__updateCurrTerrainMatKinds()
            if not self.__vehicle.isPlayerVehicle:
                if self.siegeEffects is not None:
                    self.siegeEffects.tick(_PERIODIC_TIME)
            self.__updateEffectsLOD(distanceFromPlayer)
            if self.customEffectManager:
                self.customEffectManager.update()
            return

    def __updateEffectsLOD(self, distanceFromPlayer):
        if self.customEffectManager:
            enableExhaust = distanceFromPlayer <= _LOD_DISTANCE_EXHAUST and not self.isUnderwater
            enableTrails = distanceFromPlayer <= _LOD_DISTANCE_TRAIL_PARTICLES and BigWorld.wg_isVehicleDustEnabled()
            self.customEffectManager.enable(enableTrails, EffectSettings.SETTING_DUST)
            self.customEffectManager.enable(enableExhaust, EffectSettings.SETTING_EXHAUST)

    def __updateCurrTerrainMatKinds(self):
        if self.terrainMatKindSensor is None:
            return
        else:
            matKinds = self.terrainMatKindSensor.matKinds
            matKindsCount = len(matKinds)
            for i in xrange(_MATKIND_COUNT):
                matKind = matKinds[i] if i < matKindsCount else 0
                self.__currTerrainMatKind[i] = matKind
                effectIndex = calcEffectMaterialIndex(matKind)
                effectMaterialName = ''
                if effectIndex is not None:
                    effectMaterialName = material_kinds.EFFECT_MATERIALS[effectIndex]
                self.__terrainEffectMaterialNames[i] = effectMaterialName

            if self.vehicleTraces is not None:
                self.vehicleTraces.setCurrTerrainMatKinds(self.__currTerrainMatKind[0], self.__currTerrainMatKind[1])
            return

    def switchFireVibrations(self, bStart):
        if self.peripheralsController is not None:
            self.peripheralsController.switchFireVibrations(bStart)
        return

    def executeHitVibrations(self, hitEffectCode):
        if self.peripheralsController is not None:
            self.peripheralsController.executeHitVibrations(hitEffectCode)
        return

    def executeRammingVibrations(self, matKind = None):
        if self.peripheralsController is not None:
            self.peripheralsController.executeRammingVibrations(self.__vehicle, matKind)
        return

    def executeShootingVibrations(self, caliber):
        if self.peripheralsController is not None:
            self.peripheralsController.executeShootingVibrations(caliber)
        return

    def executeCriticalHitVibrations(self, vehicle, extrasName):
        if self.peripheralsController is not None:
            self.peripheralsController.executeCriticalHitVibrations(vehicle, extrasName)
        return

    def deviceStateChanged(self, deviceName, state):
        if not self.isUnderwater and self.detailedEngineState is not None and deviceName == 'engine':
            engineState = engine_state.getEngineStateFromName(state)
            self.detailedEngineState.engineState = engineState
        return

    def __linkCompound(self):
        vehicle = self.__vehicle
        vehicle.model = None
        vehicle.model = self.__compoundModel
        vehicleMatrix = vehicle.matrix
        self.__compoundModel.matrix = vehicleMatrix
        return

    def __createStickers(self):
        if self.__vehicleStickers is not None:
            return
        else:
            insigniaRank = self.__vehicle.publicInfo['marksOnGun']
            outfit = self.__getVehicleOutfit()
            self.__vehicleStickers = VehicleStickers(self.__typeDesc, insigniaRank, outfit)
            clanID = BigWorld.player().arena.vehicles[self.__vehicle.id]['clanDBID']
            self.__vehicleStickers.setClanID(clanID)
            return

    def __attachStickers(self, alpha = _DEFAULT_STICKERS_ALPHA, emblemsOnly = False):
        raise self.__vehicleStickers is not None or AssertionError
        self.__vehicleStickers.alpha = alpha
        self.__vehicleStickers.attach(self.compoundModel, self.__currentDamageState.isCurrentModelDamaged, not emblemsOnly)
        return

    def __createAndAttachStickers(self):
        isCurrentModelDamaged = self.__currentDamageState.isCurrentModelDamaged
        stickersAlpha = _DEFAULT_STICKERS_ALPHA
        if isCurrentModelDamaged:
            stickersAlpha = items.vehicles.g_cache.commonConfig['miscParams']['damageStickerAlpha']
        self.__createStickers()
        self.__attachStickers(stickersAlpha, isCurrentModelDamaged)

    def __attachSplodge(self, splodge):
        node = self.__compoundModel.node(TankPartNames.HULL)
        if splodge is not None and self.__splodge is None:
            self.__splodge = splodge
            node.attach(splodge)
        return

    def __disableStipple(self):
        self.compoundModel.stipple = False

    def __computeVehicleHeight(self):
        desc = self.__typeDesc
        turretBBox = desc.turret.hitTester.bbox
        gunBBox = desc.gun.hitTester.bbox
        hullBBox = desc.hull.hitTester.bbox
        hullTopY = desc.chassis.hullPosition[1] + hullBBox[1][1]
        turretTopY = desc.chassis.hullPosition[1] + desc.hull.turretPositions[0][1] + turretBBox[1][1]
        gunTopY = desc.chassis.hullPosition[1] + desc.hull.turretPositions[0][1] + desc.turret.gunPosition[1] + gunBBox[1][1]
        return (max(hullTopY, max(turretTopY, gunTopY)), math.fabs(gunBBox[1][2] - gunBBox[0][2]))

    def setupGunMatrixTargets(self, target = None):
        if target is None:
            target = self.__filter
        self.turretMatrix.target = target.turretMatrix
        self.gunMatrix.target = target.gunMatrix
        return

    def onFriction(self, otherID, frictionPoint, state):
        if self.frictionAudition is not None:
            self.frictionAudition.processFriction(otherID, frictionPoint, state)
        return

    def assembleStipple(self):
        compound = self.compoundModel
        compound.matrix = Math.Matrix(compound.matrix)
        hullNode = compound.node(TankPartNames.HULL)
        compound.node(TankPartNames.HULL, hullNode.localMatrix)
        turretRotation = compound.node(TankPartNames.TURRET)
        if turretRotation is not None:
            compound.node(TankPartNames.TURRET, turretRotation.localMatrix)
        gunInclination = compound.node(TankNodeNames.GUN_INCLINATION)
        if gunInclination is not None:
            compound.node(TankNodeNames.GUN_INCLINATION, gunInclination.localMatrix)
        gunRecoil = compound.node(TankNodeNames.GUN_RECOIL)
        if gunRecoil is not None:
            compound.node(TankNodeNames.GUN_RECOIL, gunRecoil.localMatrix)
        self.fashions = VehiclePartsTuple(None, None, None, None)
        return

    def onSiegeStateChanged(self, newState):
        if self.engineAudition is not None:
            self.engineAudition.onSiegeStateChanged(newState)
        if self.suspensionController is not None:
            self.suspensionController.onSiegeStateChanged(newState)
        if self.suspensionSound is not None:
            self.__suspensionSound.vehicleState = newState
        if self.siegeEffects is not None:
            self.siegeEffects.onSiegeStateChanged(newState)
        return

    def __onCameraChanged(self, cameraName, currentVehicleId = None):
        if self.engineAudition is not None:
            self.engineAudition.onCameraChanged(cameraName, currentVehicleId if currentVehicleId is not None else 0)
        return

    def __onEngineStateGearUp(self):
        if self.customEffectManager is not None:
            self.customEffectManager.onGearUp()
        if self.engineAudition is not None:
            self.engineAudition.onEngineGearUp()
        return
class CompoundAppearance(ComponentSystem, CallbackDelayer):
    distanceFromPlayer = property(lambda self: self.__distanceFromPlayer)
    compoundModel = property(lambda self: self.__compoundModel)
    boundEffects = property(lambda self: self.__boundEffects)
    fashion = property(lambda self: self.__fashions.chassis)
    filter = property(lambda self: self.__filter)
    typeDescriptor = property(lambda self: self.__typeDesc)
    id = property(lambda self: self.__vID)
    isAlive = property(lambda self: self.__isAlive)

    def __setFashions(self, fashions, isTurretDetached = False):
        self.__fashions = fashions
        self.__fashion = fashions.chassis
        if isTurretDetached:
            self.__compoundModel.setupFashions((fashions.chassis, fashions.hull))
        else:
            self.__compoundModel.setupFashions(fashions)

    fashions = property(lambda self: self.__fashions, __setFashions)
    terrainMatKind = property(lambda self: self.__currTerrainMatKind)
    isInWater = property(lambda self: self.__isInWater)
    isUnderwater = property(lambda self: self.__isUnderWater)
    waterHeight = property(lambda self: self.__waterHeight)
    damageState = property(lambda self: self.__currentDamageState)
    frameTimeStamp = 0
    engineMode = property(lambda self: self.__engineMode)
    rightTrackScroll = property(lambda self: self.__rightTrackScroll)
    leftTrackScroll = property(lambda self: self.__leftTrackScroll)
    gunSound = property(lambda self: self.__gunSound)
    isPillbox = property(lambda self: self.__isPillbox)
    activated = property(lambda self: self.__activated)

    @property
    def rpm(self):
        if self.detailedEngineState is not None:
            return self.detailedEngineState.rpm
        else:
            return 0.0
            return

    @property
    def gear(self):
        if self.detailedEngineState is not None:
            return self.detailedEngineState.gearNum
        else:
            return 0.0
            return

    trackScrollController = property(lambda self: self.__trackScrollCtl)
    detailedEngineState = ComponentDescriptor()
    engineAudition = ComponentDescriptor()
    trackCrashAudition = ComponentDescriptor()
    customEffectManager = ComponentDescriptor()
    highlighter = ComponentDescriptor()
    gunRecoil = ComponentDescriptor()
    swingingAnimator = ComponentDescriptor()
    lodCalculator = ComponentDescriptor()

    def __init__(self):
        CallbackDelayer.__init__(self)
        ComponentSystem.__init__(self)
        self.turretMatrix = Math.WGAdaptiveMatrixProvider()
        self.gunMatrix = Math.WGAdaptiveMatrixProvider()
        self.__vehicle = None
        self.__filter = None
        self.__originalFilter = None
        self.__typeDesc = None
        self.__waterHeight = -1.0
        self.__isInWater = False
        self.__isUnderWater = False
        self.__splashedWater = False
        self.__vibrationsCtrl = None
        self.__lightFxCtrl = None
        self.__auxiliaryFxCtrl = None
        self.__fashion = None
        self.__crashedTracksCtrl = None
        self.__gunRecoil = None
        self.__currentDamageState = VehicleDamageState()
        self.__loadingProgress = 0
        self.__effectsPlayer = None
        self.__engineMode = (0, 0)
        self.__swingMoveFlags = 0
        self.__currTerrainMatKind = [-1] * _MATKIND_COUNT
        self.__leftLightRotMat = None
        self.__rightLightRotMat = None
        self.__leftFrontLight = None
        self.__rightFrontLight = None
        self.__prevVelocity = None
        self.__prevTime = None
        self.__isPillbox = False
        self.__chassisOcclusionDecal = OcclusionDecal()
        self.__chassisShadowForwardDecal = ShadowForwardDecal()
        self.__splodge = None
        self.__vehicleStickers = None
        self.onModelChanged = Event()
        self.__speedInfo = Math.Vector4(0.0, 0.0, 0.0, 0.0)
        self.__wasOnSoftTerrain = False
        self.__vehicleMatrixProv = None
        self.__leftTrackScroll = 0.0
        self.__rightTrackScroll = 0.0
        self.__distanceFromPlayer = 0.0
        self.__fashions = None
        self.__compoundModel = None
        self.__boundEffects = None
        self.__swingingAnimator = None
        self.__splineTracks = None
        self.__customEffectManager = None
        self.__trackScrollCtl = BigWorld.PyTrackScroll()
        self.__weaponEnergy = 0.0
        self.__activated = False
        self.__systemStarted = False
        self.__vID = 0
        self.__isAlive = True
        self.__isTurretDetached = False
        self.__trackFashionSet = False
        self.__periodicTimerID = None
        self.__wasDeactivated = False
        return

    def prerequisites(self, typeDescriptor, vID, health, isCrewActive, isTurretDetached):
        self.__currentDamageState.update(health, isCrewActive, self.__isUnderWater)
        out = []
        out.append(typeDescriptor.type.camouflageExclusionMask)
        splineDesc = typeDescriptor.chassis['splineDesc']
        if splineDesc is not None:
            out.append(splineDesc['segmentModelLeft'])
            out.append(splineDesc['segmentModelRight'])
            if splineDesc['segment2ModelLeft'] is not None:
                out.append(splineDesc['segment2ModelLeft'])
            if splineDesc['segment2ModelRight'] is not None:
                out.append(splineDesc['segment2ModelRight'])
        customization = items.vehicles.g_cache.customization(typeDescriptor.type.customizationNationID)
        camouflageParams = self.__getCamouflageParams(typeDescriptor, vID)
        if camouflageParams is not None and customization is not None:
            camouflageId = camouflageParams[0]
            camouflageDesc = customization['camouflages'].get(camouflageId)
            if camouflageDesc is not None and camouflageDesc['texture'] != '':
                out.append(camouflageDesc['texture'])
                for tgDesc in (typeDescriptor.turret, typeDescriptor.gun):
                    exclMask = tgDesc.get('camouflageExclusionMask')
                    if exclMask is not None and exclMask != '':
                        out.append(exclMask)

        self.__vID = vID
        self.__typeDesc = typeDescriptor
        self.__isTurretDetached = isTurretDetached
        return out

    def setVehicle(self, vehicle):
        self.__vehicle = vehicle
        if self.__customEffectManager is not None:
            self.__customEffectManager.setVehicle(vehicle)
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.setVehicle(vehicle)
        self.highlighter.setVehicle(vehicle)
        return

    def activate(self):
        if self.__activated or self.__vehicle is None:
            return
        else:
            if self.__currentDamageState.isCurrentModelDamaged:
                if self.__customEffectManager is not None:
                    self.__customEffectManager.destroy()
                    self.__customEffectManager = None
                if self.detailedEngineState is not None:
                    self.detailedEngineState.destroy()
                    self.detailedEngineState = None
            super(CompoundAppearance, self).activate()
            isPlayerVehicle = self.__vehicle.isPlayerVehicle
            isObserver = 'observer' in self.__typeDesc.type.tags
            player = BigWorld.player()
            self.__originalFilter = self.__vehicle.filter
            self.__createFilter()
            self.__vehicle.filter = self.__filter
            self.__vehicle.filter.enableNewPhysics(True)
            self.__vehicle.filter.enableStabilisedMatrix(isPlayerVehicle)
            self.__filter.isStrafing = self.__vehicle.isStrafing
            self.__filter.vehicleCollisionCallback = player.handleVehicleCollidedVehicle
            self.__compoundModel.isHighPriorityReflection = isPlayerVehicle
            self.__vehicleMatrixProv = self.__compoundModel.matrix
            if isPlayerVehicle:
                if player.inputHandler is not None:
                    player.inputHandler.addVehicleToCameraCollider(self.__vehicle)
                BigWorld.setSpeedTreeCollisionBody(self.__compoundModel.getBoundsForPart(TankPartIndexes.HULL))
            self.__linkCompound()
            self.__chassisShadowForwardDecal.attach(self.__typeDesc, self.__compoundModel)
            if not isObserver:
                self.__chassisOcclusionDecal.attach(self.__typeDesc, self.__compoundModel)
            self.__createStickers()
            if self.__currentDamageState.isCurrentModelDamaged:
                self.__attachStickers(items.vehicles.g_cache.commonConfig['miscParams']['damageStickerAlpha'], True)
            else:
                self.__startSystems()
                self.__attachStickers()
                self.setupGunMatrixTargets()
                if not isObserver:
                    self.__vehicle.filter.enableLagDetection(True)
            self.onModelChanged()
            if self.lodCalculator is not None:
                self.lodCalculator.setupPosition(DataLinks.linkMatrixTranslation(self.__compoundModel.matrix))
            if hasattr(self.filter, 'placingCompensationMatrix') and self.swingingAnimator is not None:
                self.swingingAnimator.placingCompensationMatrix = self.filter.placingCompensationMatrix
                self.swingingAnimator.worldMatrix = self.__compoundModel.matrix
            if self.__periodicTimerID is not None:
                BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = BigWorld.callback(_PERIODIC_TIME, self.__onPeriodicTimer)
            self.__activated = True
            if 'observer' in self.__vehicle.typeDescriptor.type.tags:
                self.__compoundModel.visible = False
            if self.__currentDamageState.isCurrentModelDamaged:
                if not self.__wasDeactivated:
                    if not self.__vehicle.isPlayerVehicle:
                        self.setupGunMatrixTargets()
                    else:
                        self.setupGunMatrixTargets(self.__vehicle.filter)
            return

    def deactivate(self, stopEffects = True):
        if not self.__activated:
            return
        else:
            self.__activated = False
            self.__wasDeactivated = True
            self.__stopSystems()
            super(CompoundAppearance, self).deactivate()
            self.__chassisOcclusionDecal.detach()
            self.__chassisShadowForwardDecal.detach()
            self.__vibrationsCtrl = None
            if self.__vehicle.isPlayerVehicle:
                BigWorld.setSpeedTreeCollisionBody(None)
            BigWorld.player().inputHandler.removeVehicleFromCameraCollider(self.__vehicle)
            self.__vehicle.filter.enableLagDetection(False)
            self.turretMatrix.setStaticTransform(self.turretMatrix)
            self.turretMatrix.target = None
            self.gunMatrix.setStaticTransform(self.gunMatrix)
            self.gunMatrix.target = None
            self.__vehicle.filter = self.__originalFilter
            self.__filter = None
            self.__originalFilter = None
            self.__vehicleStickers.detach()
            if stopEffects:
                self.__stopEffects()
                self.__boundEffects.stop()
            self.__vehicle.model = None
            self.__compoundModel.matrix = Math.Matrix()
            self.__vehicle = None
            self.__vehicleMatrixProv = None
            if self.__crashedTracksCtrl is not None:
                self.__crashedTracksCtrl.deactivate()
            return

    def __startSystems(self):
        if self.__trackScrollCtl is not None:
            self.__trackScrollCtl.activate()
            self.__trackScrollCtl.setData(self.__vehicle.filter, self.fashions.chassis)
        if self.__vehicle.isPlayerVehicle:
            self.delayCallback(_PERIODIC_TIME_ENGINE, self.__onPeriodicTimerEngine)
            self.highlighter.highlight(True)
        if self.detailedEngineState is not None:
            self.detailedEngineState.setVehicle(self.__vehicle)
            self.detailedEngineState.activate()
        if self.engineAudition is not None:
            self.engineAudition.vehicleFilter = self.filter
            self.engineAudition.activate()
            self.engineAudition.attachToModel(self.__compoundModel, self.__weaponEnergy)
        if self.__customEffectManager is not None:
            self.__customEffectManager.activate()
        if self.__vehicle.isAlive() and self.__vehicle.isPlayerVehicle:
            if self.__vibrationsCtrl is None:
                self.__vibrationsCtrl = VibrationControllersManager()
            if LightFx.LightManager.g_instance is not None and LightFx.LightManager.g_instance.isEnabled():
                self.__lightFxCtrl = LightFxControllersManager(self.__vehicle)
            if AuxiliaryFx.g_instance is not None:
                self.__auxiliaryFxCtrl = AuxiliaryFx.g_instance.createFxController(self.__vehicle)
        self.__systemStarted = True
        return

    def __stopSystems(self):
        if not self.__systemStarted:
            return
        else:
            self.__systemStarted = False
            if self.__periodicTimerID is not None:
                BigWorld.cancelCallback(self.__periodicTimerID)
                self.__periodicTimerID = None
            if self.detailedEngineState is not None:
                self.detailedEngineState.deactivate()
            if self.__vehicle.isPlayerVehicle:
                self.highlighter.highlight(False)
                self.stopCallback(self.__onPeriodicTimerEngine)
            if self.__trackScrollCtl is not None:
                self.__trackScrollCtl.deactivate()
                self.__trackScrollCtl.setData(None, None)
            if self.engineAudition is not None:
                self.engineAudition.deactivate()
            if self.trackCrashAudition is not None:
                self.trackCrashAudition.deactivate()
            if self.__customEffectManager is not None:
                self.__customEffectManager.deactivate()
            if self.engineAudition is not None:
                self.engineAudition.destroy()
            if self.__lightFxCtrl is not None:
                self.__lightFxCtrl.destroy()
                self.__lightFxCtrl = None
            if self.__auxiliaryFxCtrl is not None:
                self.__auxiliaryFxCtrl.destroy()
                self.__auxiliaryFxCtrl = None
            return

    def __destroySystems(self):
        if self.__periodicTimerID is not None:
            BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = None
        if self.customEffectManager is not None:
            self.customEffectManager.destroy()
            self.customEffectManager = None
        if self.__trackScrollCtl is not None:
            self.__trackScrollCtl.deactivate()
            self.__trackScrollCtl = None
        if self.engineAudition is not None:
            self.engineAudition.destroy()
            self.engineAudition = None
        if self.__vibrationsCtrl is not None:
            self.__vibrationsCtrl.destroy()
            self.__vibrationsCtrl = None
        if self.__lightFxCtrl is not None:
            self.__lightFxCtrl.destroy()
            self.__lightFxCtrl = None
        if self.__auxiliaryFxCtrl is not None:
            self.__auxiliaryFxCtrl.destroy()
            self.__auxiliaryFxCtrl = None
        if self.detailedEngineState is not None:
            self.detailedEngineState.destroy()
            self.detailedEngineState = None
        self.trackCrashAudition = None
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.destroy()
            self.__crashedTracksCtrl = None
        return

    def destroy(self):
        self.__trackScrollCtl = None
        if self.__vehicle is not None:
            self.deactivate()
        self.__destroySystems()
        ComponentSystem.destroy(self)
        self.__typeDesc = None
        self.highlighter.destroy()
        if self.__vibrationsCtrl is not None:
            self.__vibrationsCtrl.destroy()
            self.__vibrationsCtrl = None
        if self.__lightFxCtrl is not None:
            self.__lightFxCtrl.destroy()
            self.__lightFxCtrl = None
        if self.__auxiliaryFxCtrl is not None:
            self.__auxiliaryFxCtrl.destroy()
            self.__auxiliaryFxCtrl = None
        if self.__boundEffects is not None:
            self.__boundEffects.destroy()
        self.__vehicleStickers = None
        self.onModelChanged = None
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.destroy()
            self.__crashedTracksCtrl = None
        self.__chassisOcclusionDecal.destroy()
        self.__chassisOcclusionDecal = None
        self.__chassisShadowForwardDecal.destroy()
        self.__chassisShadowForwardDecal = None
        self.__compoundModel = None
        CallbackDelayer.destroy(self)
        return

    def __createFilter(self):
        self.__isPillbox = 'pillbox' in self.__typeDesc.type.tags
        if self.__isPillbox:
            self.__filter = BigWorld.WGPillboxFilter()
        else:
            self.__filter = BigWorld.WGVehicleFilter()
            self.__filter.vehicleWidth = self.__typeDesc.chassis['topRightCarryingPoint'][0] * 2
            self.__filter.maxMove = self.__typeDesc.physics['speedLimits'][0] * 2.0
            self.__filter.vehicleMinNormalY = self.__typeDesc.physics['minPlaneNormalY']
            for p1, p2, p3 in self.__typeDesc.physics['carryingTriangles']:
                self.__filter.addTriangle((p1[0], 0, p1[1]), (p2[0], 0, p2[1]), (p3[0], 0, p3[1]))

            fashion = self.__fashions[0]
            if fashion is not None:
                fashion.physicsInfo = self.__filter.physicsInfo
                fashion.movementInfo = self.__filter.movementInfo
            else:
                self.__trackFashionSet = False
            self.__filter.placingOnGround = self.__filter.placingOnGround if self.__trackFashionSet else False
        return

    def start(self, prereqs = None):
        if prereqs is None:
            self.__typeDesc.chassis['hitTester'].loadBspModel()
            self.__typeDesc.hull['hitTester'].loadBspModel()
            self.__typeDesc.turret['hitTester'].loadBspModel()
        for hitTester in self.__typeDesc.getHitTesters():
            hitTester.loadBspModel()

        self.__compoundModel = prereqs[self.__typeDesc.name]
        self.__boundEffects = bound_effects.ModelBoundEffects(self.__compoundModel)
        fashions = camouflages.prepareFashions(self.__typeDesc, self.__currentDamageState.isCurrentModelDamaged, self.__getCamouflageParams(self.__typeDesc, self.__vID)[0])
        if not self.__currentDamageState.isCurrentModelDamaged:
            self.__trackFashionSet = _setupVehicleFashion(fashions[0], self.__typeDesc)
        self.__compoundModel.setupFashions(fashions)
        fashions = camouflages.applyCamouflage(self.__typeDesc, fashions, self.__currentDamageState.isCurrentModelDamaged, self.__getCamouflageParams(self.__typeDesc, self.__vID)[0])
        fashions = camouflages.applyRepaint(self.__typeDesc, fashions)
        self.__setFashions(fashions, self.__isTurretDetached)
        self.__setupModels()
        if not VehicleDamageState.isDamagedModel(self.__currentDamageState.modelState):
            self.__splineTracks = setupSplineTracks(self.__fashion, self.__typeDesc, self.__compoundModel, prereqs)
        if self.__currentDamageState.effect is not None:
            self.__playEffect(self.__currentDamageState.effect, SpecialKeyPointNames.STATIC)
        if self.__currentDamageState.isCurrentModelDamaged:
            self.__trackScrollCtl = None
            self.__crashedTracksCtrl = None
        else:
            self.__crashedTracksCtrl = CrashedTrackController(self.__typeDesc, self.__fashion)
        return

    def showStickers(self, show):
        self.__vehicleStickers.show = show

    def updateTurretVisibility(self):
        self.__requestModelsRefresh()

    def changeVisibility(self, modelVisible):
        self.compoundModel.visible = modelVisible
        self.showStickers(modelVisible)
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.setVisible(modelVisible)
        return

    def changeDrawPassVisibility(self, visibilityMask):
        colorPassEnabled = visibilityMask & BigWorld.ColorPassBit != 0
        self.compoundModel.visible = visibilityMask
        self.compoundModel.skipColorPass = not colorPassEnabled
        self.showStickers(colorPassEnabled)
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.setVisible(visibilityMask)
        return

    def onVehicleHealthChanged(self):
        vehicle = self.__vehicle
        if not vehicle.isAlive():
            if vehicle.health > 0:
                self.changeEngineMode((0, 0))
        currentState = self.__currentDamageState
        previousState = currentState.state
        currentState.update(vehicle.health, vehicle.isCrewActive, self.__isUnderWater)
        if previousState != currentState.state:
            if currentState.effect is not None:
                self.__playEffect(currentState.effect)
            if vehicle.health <= 0:
                BigWorld.player().inputHandler.onVehicleDeath(vehicle, currentState.state == 'ammoBayExplosion')
                self.processVehicleDeath(currentState)
                self.__requestModelsRefresh()
        return

    @ComponentSystem.groupCall
    def processVehicleDeath(self, vehicleDamageState):
        pass

    def showAmmoBayEffect(self, mode, fireballVolume):
        if mode == constants.AMMOBAY_DESTRUCTION_MODE.POWDER_BURN_OFF:
            self.__playEffect('ammoBayBurnOff')
            return
        volumes = items.vehicles.g_cache.commonConfig['miscParams']['explosionCandleVolumes']
        candleIdx = 0
        for idx, volume in enumerate(volumes):
            if volume >= fireballVolume:
                break
            candleIdx = idx + 1

        if candleIdx > 0:
            self.__playEffect('explosionCandle%d' % candleIdx)
        else:
            self.__playEffect('explosion')

    def changeEngineMode(self, mode, forceSwinging = False):
        self.__engineMode = mode
        if self.detailedEngineState is not None:
            self.detailedEngineState.setMode(self.__engineMode[0])
        if self.__trackScrollCtl is not None:
            self.__trackScrollCtl.setMode(self.__engineMode)
        if BattleReplay.isPlaying() and BattleReplay.g_replayCtrl.isTimeWarpInProgress:
            return
        else:
            isOldPhysicsMode = self.__vehicle.physicsMode == VEHICLE_PHYSICS_MODE.STANDARD
            if isOldPhysicsMode and forceSwinging:
                flags = mode[1]
                prevFlags = self.__swingMoveFlags
                swingingAnimator = self.__swingingAnimator
                if swingingAnimator is not None:
                    moveMask = 3
                    rotMask = 12
                    if flags & moveMask ^ prevFlags & moveMask:
                        swingPeriod = 2.0
                        if flags & 1:
                            swingingAnimator.accelSwingingDirection = -1
                        elif flags & 2:
                            swingingAnimator.accelSwingingDirection = 1
                        else:
                            swingingAnimator.accelSwingingDirection = 0
                    elif not flags & moveMask and flags & rotMask ^ prevFlags & rotMask:
                        swingPeriod = 1.0
                        swingingAnimator.accelSwingingDirection = 0
                    else:
                        swingPeriod = 0.0
                    if swingPeriod > swingingAnimator.accelSwingingPeriod:
                        swingingAnimator.accelSwingingPeriod = swingPeriod
                self.__swingMoveFlags = flags
            return

    def stopSwinging(self):
        if self.__swingingAnimator is not None:
            self.__swingingAnimator.accelSwingingPeriod = 0.0
        return

    def removeDamageSticker(self, code):
        self.__vehicleStickers.delDamageSticker(code)

    def addDamageSticker(self, code, componentName, stickerID, segStart, segEnd):
        self.__vehicleStickers.addDamageSticker(code, componentName, stickerID, segStart, segEnd)

    def receiveShotImpulse(self, dir, impulse):
        if BattleReplay.isPlaying() and BattleReplay.g_replayCtrl.isTimeWarpInProgress:
            return
        else:
            if not VehicleDamageState.isDamagedModel(self.__currentDamageState.modelState):
                self.__swingingAnimator.receiveShotImpulse(dir, impulse)
                if self.__crashedTracksCtrl is not None:
                    self.__crashedTracksCtrl.receiveShotImpulse(dir, impulse)
            return

    def recoil(self):
        gunNode = self.compoundModel.node(TankNodeNames.GUN_INCLINATION)
        impulseDir = Math.Matrix(gunNode).applyVector(Math.Vector3(0, 0, -1))
        impulseValue = self.__typeDesc.gun['impulse']
        self.receiveShotImpulse(impulseDir, impulseValue)
        self.__gunRecoil.recoil()
        node = self.compoundModel.node('HP_gunFire')
        gunPos = Math.Matrix(node).translation
        BigWorld.player().inputHandler.onVehicleShaken(self.__vehicle, gunPos, impulseDir, self.__typeDesc.shot['shell']['caliber'], ShakeReason.OWN_SHOT_DELAYED)

    def addCrashedTrack(self, isLeft):
        if not self.__vehicle.isAlive():
            return
        else:
            if self.__crashedTracksCtrl is not None:
                self.__crashedTracksCtrl.addTrack(isLeft)
            if not self.__vehicle.isEnteringWorld and self.trackCrashAudition:
                self.trackCrashAudition.playCrashSound(isLeft)
            return

    def delCrashedTrack(self, isLeft):
        if self.__crashedTracksCtrl is not None:
            self.__crashedTracksCtrl.delTrack(isLeft)
        if not self.__vehicle.isEnteringWorld and self.trackCrashAudition and self.__vehicle.isPlayerVehicle:
            self.trackCrashAudition.playCrashSound(isLeft, True)
        return

    def __requestModelsRefresh(self):
        currentModelState = self.__currentDamageState.modelState
        assembler = model_assembler.prepareCompoundAssembler(self.__typeDesc, currentModelState, self.__vehicle.spaceID, self.__vehicle.isTurretDetached)
        BigWorld.loadResourceListBG([assembler], functools.partial(self.__onModelsRefresh, currentModelState))

    def __onModelsRefresh(self, modelState, resourceList):
        if BattleReplay.isFinished():
            return
        elif modelState != self.__currentDamageState.modelState:
            self.__requestModelsRefresh()
            return
        elif self.__vehicle is None:
            return
        else:
            vehicle = self.__vehicle
            newCompoundModel = resourceList[self.__typeDesc.name]
            self.deactivate(False)
            self.__compoundModel = newCompoundModel
            self.__isTurretDetached = vehicle.isTurretDetached
            if self.__currentDamageState.isCurrentModelDamaged:
                fashions = VehiclePartsTuple(None, None, None, None)
                self.swingingAnimator = None
                self.gunRecoil = None
                self.__setFashions(fashions, self.__isTurretDetached)
                self.__destroySystems()
                self.__trackFashionSet = False
            self.__setupModels()
            self.setVehicle(vehicle)
            self.activate()
            self.__reattachComponents(self.__compoundModel)
            lodLink = DataLinks.createFloatLink(self.lodCalculator, 'lodDistance')
            if not self.damageState.isCurrentModelDamaged:
                model_assembler.assembleRecoil(self, lodLink)
            model_assembler.setupTurretRotations(self)
            return

    def __setupModels(self):
        if not self.__currentDamageState.isCurrentModelDamaged:
            self.__gunFireNode = self.__compoundModel.node('HP_gunFire')
            self.__isAlive = True
            _, gunLength = self.__computeVehicleHeight()
            self.__weaponEnergy = gunLength * self.__typeDesc.shot['shell']['caliber']
            self.__setupHavok()
        else:
            self.__isAlive = False
            self.__gunFireNode = None
        if MAX_DISTANCE > 0:
            transform = self.__typeDesc.chassis['AODecals'][0]
            self.__attachSplodge(BigWorld.Splodge(transform, MAX_DISTANCE, self.__typeDesc.chassis['hullPosition'].y))
        return

    def __setupHavok(self):
        vDesc = self.__typeDesc
        node = self.compoundModel.node(TankPartNames.HULL)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.hull['hitTester'].getBspModel())
        if hkm is not None:
            node.attach(hkm)
        node = self.compoundModel.node(TankPartNames.TURRET)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.turret['hitTester'].getBspModel())
        if hkm is not None:
            node.attach(hkm)
        node = self.compoundModel.node(TankPartNames.CHASSIS)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.chassis['hitTester'].getBspModel())
        if hkm is not None:
            node.attach(hkm)
        node = self.compoundModel.node(TankPartNames.GUN)
        hkm = BigWorld.wg_createHKAttachment(node, vDesc.gun['hitTester'].getBspModel())
        if hkm is not None:
            node.attach(hkm)
        return

    def __reattachComponents(self, model):
        self.__boundEffects.reattachTo(model)
        if self.__effectsPlayer is not None:
            self.__effectsPlayer.reattachTo(model)
        if self.engineAudition is not None:
            self.engineAudition.attachToModel(model)
        return

    def __playEffect(self, kind, *modifs):
        self.__stopEffects()
        if kind == 'empty' or self.__vehicle is None:
            return
        else:
            enableDecal = True
            if not self.__isPillbox and kind in ('explosion', 'destruction'):
                filter = self.__vehicle.filter
                isFlying = filter.numLeftTrackContacts < 2 and filter.numRightTrackContacts < 2
                if isFlying:
                    enableDecal = False
            if self.isUnderwater:
                if kind not in ('submersionDeath',):
                    return
            effects = self.typeDescriptor.type.effects[kind]
            if not effects:
                return
            vehicle = self.__vehicle
            effects = random.choice(effects)
            self.__effectsPlayer = EffectsListPlayer(effects[1], effects[0], showShockWave=vehicle.isPlayerVehicle, showFlashBang=vehicle.isPlayerVehicle, isPlayer=vehicle.isPlayerVehicle, showDecal=enableDecal, start=vehicle.position + Math.Vector3(0.0, -1.0, 0.0), end=vehicle.position + Math.Vector3(0.0, 1.0, 0.0), entity_id=vehicle.id)
            self.__effectsPlayer.play(self.__compoundModel, *modifs)
            return

    __SPORT_ACTIONS_CAMOUFLAGES = {'ussr:T62A_sport': (95, 94),
     'usa:M24_Chaffee_GT': (82, 83)}

    def __getCamouflageParams(self, vDesc, vID):
        vehicleInfo = BigWorld.player().arena.vehicles.get(vID)
        if vehicleInfo is not None:
            camouflageIdPerTeam = VehicleAppearance.SPORT_ACTIONS_CAMOUFLAGES.get(vDesc.name)
            if camouflageIdPerTeam is not None:
                camouflageId = camouflageIdPerTeam[0] if vehicleInfo['team'] == 1 else camouflageIdPerTeam[1]
                return (camouflageId, time.time(), 100.0)
            camouflagePseudoname = vehicleInfo['events'].get('hunting', None)
            if camouflagePseudoname is not None:
                camouflIdsByNation = {0: {'black': 29,
                     'gold': 30,
                     'red': 31,
                     'silver': 32},
                 1: {'black': 25,
                     'gold': 26,
                     'red': 27,
                     'silver': 28},
                 2: {'black': 52,
                     'gold': 50,
                     'red': 51,
                     'silver': 53},
                 3: {'black': 48,
                     'gold': 46,
                     'red': 47,
                     'silver': 49},
                 4: {'black': 60,
                     'gold': 58,
                     'red': 59,
                     'silver': 61},
                 5: {'black': 56,
                     'gold': 54,
                     'red': 55,
                     'silver': 57},
                 6: {'black': 133,
                     'gold': 134,
                     'red': 135,
                     'silver': 136},
                 7: {'black': 141,
                     'gold': 142,
                     'red': 143,
                     'silver': 144},
                 8: {'black': 154,
                     'gold': 155,
                     'red': 156,
                     'silver': 157}}
                camouflIds = camouflIdsByNation.get(vDesc.type.customizationNationID)
                if camouflIds is not None:
                    ret = camouflIds.get(camouflagePseudoname)
                    if ret is not None:
                        return (ret, time.time(), 100.0)
        arenaType = BigWorld.player().arena.arenaType
        camouflageKind = arenaType.vehicleCamouflageKind
        return vDesc.camouflages[camouflageKind]

    def __stopEffects(self):
        if self.__effectsPlayer is not None:
            self.__effectsPlayer.stop()
        self.__effectsPlayer = None
        return

    def __calcIsUnderwater(self):
        if not self.__isInWater:
            return False
        turretOffs = self.__vehicle.typeDescriptor.chassis['hullPosition'] + self.__vehicle.typeDescriptor.hull['turretPositions'][0]
        turretOffsetMat = Math.Matrix()
        turretOffsetMat.setTranslate(turretOffs)
        turretJointMat = Math.Matrix(self.__vehicle.matrix)
        turretJointMat.preMultiply(turretOffsetMat)
        turretHeight = turretJointMat.translation.y - self.__vehicle.position.y
        return turretHeight < self.__waterHeight

    def __updateWaterStatus(self):
        vehiclePosition = self.__vehicle.position
        self.__waterHeight = BigWorld.wg_collideWater(vehiclePosition, vehiclePosition + Math.Vector3(0.0, 1.0, 0.0), False)
        self.__isInWater = self.__waterHeight != -1
        self.__isUnderWater = self.__calcIsUnderwater()
        wasSplashed = self.__splashedWater
        waterHitPoint = None
        if self.__isInWater:
            self.__splashedWater = True
            waterHitPoint = vehiclePosition + Math.Vector3(0.0, self.__waterHeight, 0.0)
        else:
            trPoint = self.__typeDesc.chassis['topRightCarryingPoint']
            cornerPoints = (Math.Vector3(trPoint.x, 0.0, trPoint.y),
             Math.Vector3(trPoint.x, 0.0, -trPoint.y),
             Math.Vector3(-trPoint.x, 0.0, -trPoint.y),
             Math.Vector3(-trPoint.x, 0.0, trPoint.y))
            vehMat = Math.Matrix(self.__vehicleMatrixProv)
            for cornerPoint in cornerPoints:
                pointToTest = vehMat.applyPoint(cornerPoint)
                dist = BigWorld.wg_collideWater(pointToTest, pointToTest + Math.Vector3(0.0, 1.0, 0.0))
                if dist != -1:
                    self.__splashedWater = True
                    waterHitPoint = pointToTest + Math.Vector3(0.0, dist, 0.0)
                    break

            self.__splashedWater = False
        if self.__splashedWater and not wasSplashed:
            lightVelocityThreshold = self.__typeDesc.type.collisionEffectVelocities['waterContact']
            heavyVelocityThreshold = self.__typeDesc.type.heavyCollisionEffectVelocities['waterContact']
            vehicleVelocity = abs(self.__speedInfo[0])
            if vehicleVelocity >= lightVelocityThreshold:
                collRes = BigWorld.wg_collideSegment(self.__vehicle.spaceID, waterHitPoint, waterHitPoint + (0.0, -_MIN_DEPTH_FOR_HEAVY_SPLASH, 0.0), 18, 8)
                deepEnough = collRes is None
                effectName = 'waterCollisionLight' if vehicleVelocity < heavyVelocityThreshold or not deepEnough else 'waterCollisionHeavy'
                self.__vehicle.showCollisionEffect(waterHitPoint, effectName, Math.Vector3(0.0, 1.0, 0.0))
        if self.__effectsPlayer is not None:
            if self.isUnderwater != (self.__currentDamageState.effect in ('submersionDeath',)):
                self.__stopEffects()
        return

    def updateTracksScroll(self, leftScroll, rightScroll):
        self.__leftTrackScroll = leftScroll
        self.__rightTrackScroll = rightScroll
        if self.__trackScrollCtl is not None:
            self.__trackScrollCtl.setExternal(leftScroll, rightScroll)
        return

    def __onPeriodicTimerEngine(self):
        if self.detailedEngineState is None or self.engineAudition is None:
            return
        else:
            self.detailedEngineState.refresh(_PERIODIC_TIME_ENGINE)
            self.engineAudition.tick()
            return _PERIODIC_TIME_ENGINE

    def __onPeriodicTimer(self):
        timeStamp = BigWorld.wg_getFrameTimestamp()
        if VehicleAppearance.frameTimeStamp >= timeStamp:
            self.__periodicTimerID = BigWorld.callback(0.0, self.__onPeriodicTimer)
            return
        else:
            VehicleAppearance.frameTimeStamp = timeStamp
            self.__periodicTimerID = BigWorld.callback(_PERIODIC_TIME, self.__onPeriodicTimer)
            if self.__isPillbox or self.__vehicle is None:
                return
            vehicle = self.__vehicle
            self.__speedInfo = vehicle.speedInfo.value
            if not self.__vehicle.isPlayerVehicle and self.detailedEngineState is not None:
                self.detailedEngineState.refresh(_PERIODIC_TIME)
            try:
                self.__updateVibrations()
                if self.__lightFxCtrl is not None:
                    self.__lightFxCtrl.update(vehicle)
                if self.__auxiliaryFxCtrl is not None:
                    self.__auxiliaryFxCtrl.update(vehicle)
                self.__updateWaterStatus()
                if not vehicle.isAlive():
                    return
                self.__distanceFromPlayer = (BigWorld.camera().position - vehicle.position).length
                extra = vehicle.typeDescriptor.extrasDict['fire']
                if extra.isRunningFor(vehicle):
                    extra.checkUnderwater(vehicle, self.isUnderwater)
                if self.__fashion is None:
                    return
                self.__updateCurrTerrainMatKinds()
                if not self.__vehicle.isPlayerVehicle:
                    self.engineAudition.tick()
                self.__updateEffectsLOD()
                vehicle.filter.placingOnGround = not self.__fashion.suspensionWorking
                if self.customEffectManager:
                    self.__customEffectManager.update()
                self.__vehicle.filter.placingOnGround = not self.__fashion.suspensionWorking
            except:
                LOG_CURRENT_EXCEPTION()

            return

    def __updateEffectsLOD(self):
        if self.customEffectManager:
            enableExhaust = self.__distanceFromPlayer <= _LOD_DISTANCE_EXHAUST
            enableTrails = self.__distanceFromPlayer <= _LOD_DISTANCE_TRAIL_PARTICLES and BigWorld.wg_isVehicleDustEnabled()
            self.__customEffectManager.enable(enableTrails, EffectSettings.SETTING_DUST)
            self.__customEffectManager.enable(enableExhaust, EffectSettings.SETTING_EXHAUST)

    def __updateCurrTerrainMatKinds(self):
        leftNode = self.compoundModel.node(TankNodeNames.TRACK_LEFT_MID)
        rightNode = self.compoundModel.node(TankNodeNames.TRACK_RIGHT_MID)
        testPoints = (Math.Matrix(leftNode).translation, Math.Matrix(rightNode).translation, self.__vehicle.position)
        isOnSoftTerrain = False
        for i in xrange(_MATKIND_COUNT):
            testPoint = testPoints[i]
            res = BigWorld.wg_collideSegment(self.__vehicle.spaceID, testPoint + Math.Vector3(0.0, 2.0, 0.0), testPoint + Math.Vector3(0.0, -2.0, 0.0), 18)
            matKind = res[2] if res is not None else 0
            self.__currTerrainMatKind[i] = matKind
            if not isOnSoftTerrain:
                groundStr = material_kinds.GROUND_STRENGTHS_BY_IDS.get(matKind)
                isOnSoftTerrain = groundStr == 'soft'

        if self.__vehicle.isPlayerVehicle and self.__wasOnSoftTerrain != isOnSoftTerrain:
            self.__wasOnSoftTerrain = isOnSoftTerrain
            if isOnSoftTerrain:
                TriggersManager.g_manager.activateTrigger(TRIGGER_TYPE.PLAYER_VEHICLE_ON_SOFT_TERRAIN)
            else:
                TriggersManager.g_manager.deactivateTrigger(TRIGGER_TYPE.PLAYER_VEHICLE_ON_SOFT_TERRAIN)
        self.__fashion.setCurrTerrainMatKinds(self.__currTerrainMatKind[0], self.__currTerrainMatKind[1])
        return

    def switchFireVibrations(self, bStart):
        if self.__vibrationsCtrl is not None:
            self.__vibrationsCtrl.switchFireVibrations(bStart)
        return

    def executeHitVibrations(self, hitEffectCode):
        if self.__vibrationsCtrl is not None:
            self.__vibrationsCtrl.executeHitVibrations(hitEffectCode)
        return

    def executeRammingVibrations(self, matKind = None):
        if self.__vibrationsCtrl is not None:
            self.__vibrationsCtrl.executeRammingVibrations(self.__vehicle.getSpeed(), matKind)
        return

    def executeShootingVibrations(self, caliber):
        if self.__vibrationsCtrl is not None:
            self.__vibrationsCtrl.executeShootingVibrations(caliber)
        return

    def executeCriticalHitVibrations(self, vehicle, extrasName):
        if self.__vibrationsCtrl is not None:
            self.__vibrationsCtrl.executeCriticalHitVibrations(vehicle, extrasName)
        return

    def deviceStateChanged(self, deviceName, state):
        if self.detailedEngineState is not None and deviceName == 'engine':
            self.detailedEngineState.deviceStateChanged(state)
        return

    def __updateVibrations(self):
        if self.__vibrationsCtrl is None:
            return
        else:
            vehicle = self.__vehicle
            if self.__crashedTracksCtrl is not None:
                crashedTrackCtrl = self.__crashedTracksCtrl
                self.__vibrationsCtrl.update(vehicle, crashedTrackCtrl.isLeftTrackBroken(), crashedTrackCtrl.isRightTrackBroken())
            return

    def __linkCompound(self):
        vehicle = self.__vehicle
        vehicle.model = None
        vehicle.model = self.__compoundModel
        vehicleMatrix = vehicle.matrix
        self.__compoundModel.matrix = vehicleMatrix
        return

    def __createStickers(self):
        if self.__vehicleStickers is not None:
            return
        else:
            insigniaRank = self.__vehicle.publicInfo['marksOnGun']
            self.__vehicleStickers = VehicleStickers(self.__typeDesc, insigniaRank)
            clanID = BigWorld.player().arena.vehicles[self.__vehicle.id]['clanDBID']
            self.__vehicleStickers.setClanID(clanID)
            return

    def __attachStickers(self, alpha = 1.0, emblemsOnly = False):
        try:
            self.__vehicleStickers.alpha = alpha
            self.__vehicleStickers.attach(self.compoundModel, self.__currentDamageState.isCurrentModelDamaged, not emblemsOnly)
        except:
            LOG_CURRENT_EXCEPTION()

    def __attachSplodge(self, splodge):
        node = self.__compoundModel.node(TankPartNames.HULL)
        if splodge is not None and self.__splodge is None:
            self.__splodge = splodge
            node.attach(splodge)
        return

    def __disableStipple(self):
        self.compoundModel.stipple = False

    def __computeVehicleHeight(self):
        desc = self.__typeDesc
        turretBBox = desc.turret['hitTester'].bbox
        gunBBox = desc.gun['hitTester'].bbox
        hullBBox = desc.hull['hitTester'].bbox
        hullTopY = desc.chassis['hullPosition'][1] + hullBBox[1][1]
        turretTopY = desc.chassis['hullPosition'][1] + desc.hull['turretPositions'][0][1] + turretBBox[1][1]
        gunTopY = desc.chassis['hullPosition'][1] + desc.hull['turretPositions'][0][1] + desc.turret['gunPosition'][1] + gunBBox[1][1]
        return (max(hullTopY, max(turretTopY, gunTopY)), math.fabs(gunBBox[1][2] - gunBBox[0][2]))

    def setupGunMatrixTargets(self, target = None):
        if target is None:
            target = self.__filter
        self.turretMatrix.target = target.turretMatrix
        self.gunMatrix.target = target.gunMatrix
        return

    def assembleStipple(self):
        compound = self.compoundModel
        compound.matrix = Math.Matrix(compound.matrix)
        hullNode = compound.node(TankPartNames.HULL)
        compound.node(TankPartNames.HULL, hullNode.localMatrix)
        turretRotation = compound.node(TankPartNames.TURRET)
        if turretRotation is not None:
            compound.node(TankPartNames.TURRET, turretRotation.localMatrix)
        gunInclination = compound.node(TankNodeNames.GUN_INCLINATION)
        if gunInclination is not None:
            compound.node(TankNodeNames.GUN_INCLINATION, gunInclination.localMatrix)
        gunRecoil = compound.node(TankNodeNames.GUN_RECOIL)
        if gunRecoil is not None:
            compound.node(TankNodeNames.GUN_RECOIL, gunRecoil.localMatrix)
        self.fashions = VehiclePartsTuple(None, None, None, None)
        return
예제 #4
0
class ClientSelectableObject(BigWorld.Entity, ComponentSystem):
    collisions = ComponentDescriptor()

    @property
    def enabled(self):
        return self.__enabled

    def __init__(self):
        BigWorld.Entity.__init__(self)
        ComponentSystem.__init__(self)
        self.__enabled = True
        self.__edged = False
        self.__clickSound = None
        self.model = None
        return

    def prerequisites(self):
        if self.modelName != '':
            bspModels = ((0, self.modelName), )
            collisionAssembler = BigWorld.CollisionAssembler(
                bspModels, self.spaceID)
            return [self.modelName, collisionAssembler]
        return []

    def onEnterWorld(self, prereqs):
        if self.modelName == '':
            return
        if self.modelName not in prereqs.failedIDs:
            model = prereqs[self.modelName]
            self.model = model
            self.filter = BigWorld.DumbFilter()
            self.model.addMotor(BigWorld.Servo(self.matrix))
            self.collisions = prereqs['collisionAssembler']
            collisionData = ((0, self.model.matrix), )
            self.collisions.connect(self.id, ColliderTypes.DYNAMIC_COLLIDER,
                                    collisionData)
        ComponentSystem.activate(self)

    def onLeaveWorld(self):
        ComponentSystem.deactivate(self)
        ComponentSystem.destroy(self)
        if self.__clickSound is not None:
            if self.__clickSound.isPlaying:
                self.__clickSound.stop()
            self.__clickSound.releaseMatrix()
            self.__clickSound = None
        self.highlight(False)
        return

    def enable(self, enabled):
        self.__enabled = enabled
        if not self.__enabled:
            self.highlight(False)

    def highlight(self, show):
        if show:
            if not self.__edged and self.__enabled:
                BigWorld.wgAddEdgeDetectEntity(self, 0, self.edgeMode, True)
                self.__edged = True
        elif self.__edged:
            BigWorld.wgDelEdgeDetectEntity(self)
            self.__edged = False

    def onClicked(self):
        if self.__clickSound is None:
            if self.clickSoundName:
                self.__clickSound = SoundGroups.g_instance.getSound3D(
                    self.model.root, self.clickSoundName)
                self.__clickSound.play()
        elif self.__clickSound.isPlaying:
            self.__clickSound.stop()
        else:
            self.__clickSound.play()
        return

    def _getModelHeight(self):
        return self.model.height
class DetachedTurret(BigWorld.Entity, ComponentSystem):
    allTurrets = list()
    collisions = ComponentDescriptor()

    def __init__(self):
        ComponentSystem.__init__(self)
        self.__vehDescr = vehicles.VehicleDescr(
            compactDescr=self.vehicleCompDescr)
        self.filter = BigWorld.WGTurretFilter()
        self.__detachConfirmationTimer = SynchronousDetachment(self)
        self.__detachConfirmationTimer.onInit()
        self.__detachmentEffects = None
        self.targetFullBounds = True
        self.targetCaps = [1]
        self.__isBeingPulledCallback = None
        self.__hitEffects = None
        self.__vehicleStickers = None
        return

    def reload(self):
        pass

    def __prepareModelAssembler(self):
        assembler = BigWorld.CompoundAssembler(self.__vehDescr.name,
                                               self.spaceID)
        turretModel = self.__vehDescr.turret.models.exploded
        gunModel = self.__vehDescr.gun.models.exploded
        assembler.addRootPart(turretModel, TankPartNames.TURRET)
        assembler.emplacePart(gunModel, TankNodeNames.GUN_JOINT,
                              TankPartNames.GUN)
        bspModels = ((TankPartNames.getIdx(TankPartNames.TURRET),
                      self.__vehDescr.turret.hitTester.bspModelName),
                     (TankPartNames.getIdx(TankPartNames.GUN),
                      self.__vehDescr.gun.hitTester.bspModelName))
        collisionAssembler = BigWorld.CollisionAssembler(
            bspModels,
            BigWorld.player().spaceID)
        return [assembler, collisionAssembler]

    def prerequisites(self):
        prereqs = self.__prepareModelAssembler()
        prereqs += self.__vehDescr.prerequisites()
        return prereqs

    def onEnterWorld(self, prereqs):
        self.model = prereqs[self.__vehDescr.name]
        self.model.matrix = self.matrix
        self.collisions = prereqs['collisionAssembler']
        self.__detachConfirmationTimer.onEnterWorld()
        self.__vehDescr.keepPrereqs(prereqs)
        turretDescr = self.__vehDescr.turret
        if self.isUnderWater == 0:
            self.__detachmentEffects = _TurretDetachmentEffects(
                self.model, turretDescr.turretDetachmentEffects,
                self.isCollidingWithWorld == 1)
            self.addComponent(self.__detachmentEffects)
        else:
            self.__detachmentEffects = None
        self.__hitEffects = _HitEffects(self.model)
        self.addComponent(self.__hitEffects)
        self.__componentsDesc = (self.__vehDescr.turret, self.__vehDescr.gun)
        from helpers.CallbackDelayer import CallbackDelayer
        self.__isBeingPulledCallback = CallbackDelayer()
        self.__isBeingPulledCallback.delayCallback(self.__checkIsBeingPulled(),
                                                   self.__checkIsBeingPulled)
        DetachedTurret.allTurrets.append(self)
        collisionData = ((TankPartNames.getIdx(TankPartNames.TURRET),
                          self.model.matrix),
                         (TankPartNames.getIdx(TankPartNames.GUN),
                          self.model.node(TankPartNames.GUN)))
        self.collisions.connect(self.id, ColliderTypes.DYNAMIC_COLLIDER,
                                collisionData)
        ComponentSystem.activate(self)
        return

    def isAlive(self):
        return False

    def removeEdge(self):
        pass

    def drawEdge(self):
        pass

    def __createAndAttachStickers(self):
        vehicle = BigWorld.entity(self.vehicleID)
        if not vehicle:
            return
        if self.__vehicleStickers:
            return
        self.__vehicleStickers = VehicleStickers(
            self.__vehDescr, vehicle.publicInfo['marksOnGun'])
        self.__vehicleStickers.alpha = vehicles.g_cache.commonConfig[
            'miscParams']['damageStickerAlpha']
        self.__vehicleStickers.attach(self.model, True, False, True)

    def onLeaveWorld(self):
        ComponentSystem.deactivate(self)
        ComponentSystem.destroy(self)
        DetachedTurret.allTurrets.remove(self)
        self.__detachConfirmationTimer.cancel()
        self.__detachConfirmationTimer = None
        self.__isBeingPulledCallback.destroy()
        self.__isBeingPulledCallback = None
        if self.__vehicleStickers is not None:
            self.__vehicleStickers.detach()
            self.__vehicleStickers = None
        return

    def onStaticCollision(self, energy, point, normal):
        if self.__detachmentEffects is not None:
            surfaceMaterial = calcSurfaceMaterialNearPoint(
                point, normal, self.spaceID)
            effectIdx = surfaceMaterial.effectIdx
            groundEffect = True
            distToWater = BigWorld.wg_collideWater(self.position,
                                                   surfaceMaterial.point)
            if distToWater != -1:
                vel = Math.Vector3(self.velocity).length
                if vel < _MIN_COLLISION_SPEED:
                    groundEffect = False
                effectIdx = material_kinds.EFFECT_MATERIAL_INDEXES_BY_NAMES[
                    'water']
            self.__detachmentEffects.notifyAboutCollision(
                energy, point, effectIdx, groundEffect, self.isUnderWater)
        return

    def showDamageFromShot(self, points, effectsIndex):
        _, decodedPoints, _ = DamageFromShotDecoder.decodeHitPoints(
            points, self.collisions)
        for shotPoint in decodedPoints:
            if shotPoint.componentName == TankPartNames.TURRET or shotPoint.componentName == TankPartNames.GUN:
                self.__hitEffects.showHit(shotPoint, effectsIndex,
                                          shotPoint.componentName)
            LOG_ERROR(
                "Detached turret got hit into %s component, but it's impossible"
                % shotPoint.componentName)

    def set_isUnderWater(self, prev):
        if self.__detachmentEffects is not None:
            if self.isUnderWater:
                self.__detachmentEffects.stopEffects()
        return

    def set_isCollidingWithWorld(self, prev):
        pass

    def changeAppearanceVisibility(self, isVisible):
        self.model.visible = isVisible

    def __checkIsBeingPulled(self):
        if self.__detachmentEffects is not None:
            if self.isCollidingWithWorld and not self.isUnderWater and self.velocity.lengthSquared > 0.1:
                extent = Math.Matrix(
                    self.model.getBoundsForRoot()).applyVector(
                        Math.Vector3(0.5, 0.5, 0.5)).length
                surfaceMaterial = calcSurfaceMaterialNearPoint(
                    self.position, Math.Vector3(0, extent, 0), self.spaceID)
                self.__detachmentEffects.notifyAboutBeingPulled(
                    True, surfaceMaterial.effectIdx)
                if surfaceMaterial.matKind == 0:
                    LOG_ERROR(
                        'calcSurfaceMaterialNearPoint failed to find the collision point at: ',
                        self.position)
            else:
                self.__detachmentEffects.notifyAboutBeingPulled(False, None)
        return SERVER_TICK_LENGTH
예제 #6
0
class HangarVehicleAppearance(ComponentSystem):
    __ROOT_NODE_NAME = 'V'
    itemsCache = dependency.descriptor(IItemsCache)
    itemsFactory = dependency.descriptor(IGuiItemsFactory)
    settingsCore = dependency.descriptor(ISettingsCore)
    wheelsAnimator = ComponentDescriptor()
    trackNodesAnimator = ComponentDescriptor()
    collisions = ComponentDescriptor()
    shadowManager = ComponentDescriptor()
    dirtComponent = ComponentDescriptor()
    tracks = ComponentDescriptor()
    collisionObstaclesCollector = ComponentDescriptor()
    tessellationCollisionSensor = ComponentDescriptor()

    @property
    def compoundModel(self):
        return self.__vEntity.model if self.__vEntity else None

    fashion = property(lambda self: self.__fashions[0])

    def __init__(self, spaceId, vEntity):
        ComponentSystem.__init__(self)
        self.__loadState = _LoadStateNotifier()
        self.__curBuildInd = 0
        self.__vDesc = None
        self.__vState = None
        self.__fashions = VehiclePartsTuple(None, None, None, None)
        self.__repaintHandlers = [None, None, None, None]
        self.__camoHandlers = [None, None, None, None]
        self.__spaceId = spaceId
        self.__vEntity = weakref.proxy(vEntity)
        self.__onLoadedCallback = None
        self.__onLoadedAfterRefreshCallback = None
        self.__vehicleStickers = None
        self.__isVehicleDestroyed = False
        self.__outfit = None
        self.shadowManager = None
        cfg = hangarCFG()
        self.__currentEmblemsAlpha = cfg['emblems_alpha_undamaged']
        self.__showMarksOnGun = self.settingsCore.getSetting('showMarksOnGun')
        self.settingsCore.onSettingsChanged += self.__onSettingsChanged
        self.itemsCache.onSyncCompleted += self.__onItemsCacheSyncCompleted
        g_eventBus.addListener(CameraRelatedEvents.CAMERA_ENTITY_UPDATED,
                               self.__handleEntityUpdated)
        return

    def recreate(self, vDesc, vState, callback):
        self.__onLoadedCallback = callback
        self.__reload(vDesc, vState, self._getActiveOutfit())

    def remove(self):
        self.__loadState.unload()
        self.__vDesc = None
        self.__vState = None
        self.__isVehicleDestroyed = False
        self.__vEntity.model = None
        if self.shadowManager:
            self.shadowManager.updatePlayerTarget(None)
        if self.collisions:
            BigWorld.removeCameraCollider(self.collisions.getColliderID())
        return

    def refresh(self, outfit=None, callback=None):
        if self.__loadState.isLoaded and self.__vDesc:
            if callback is not None:
                self.__onLoadedAfterRefreshCallback = callback
            self.__reload(self.__vDesc, self.__vState, outfit or self.__outfit)
        return

    def destroy(self):
        ComponentSystem.deactivate(self)
        ComponentSystem.destroy(self)
        self.__vDesc = None
        self.__vState = None
        self.__loadState.unload()
        self.__loadState.destroy()
        self.__loadState = None
        self.__curBuildInd = 0
        self.__vEntity = None
        self.__onLoadedCallback = None
        self.__onLoadedAfterRefreshCallback = None
        self.settingsCore.onSettingsChanged -= self.__onSettingsChanged
        self.itemsCache.onSyncCompleted -= self.__onItemsCacheSyncCompleted
        g_eventBus.removeListener(CameraRelatedEvents.CAMERA_ENTITY_UPDATED,
                                  self.__handleEntityUpdated)
        return

    @property
    def loadState(self):
        return self.__loadState

    @property
    def fakeShadowDefinedInHullTexture(self):
        return self.__vDesc.hull.hangarShadowTexture if self.__vDesc else None

    def isLoaded(self):
        return self.__loadState.isLoaded

    def computeVehicleHeight(self):
        gunLength = 0.0
        height = 0.0
        if self.collisions is not None:
            desc = self.__vDesc
            hullBB = self.collisions.getBoundingBox(
                TankPartNames.getIdx(TankPartNames.HULL))
            turretBB = self.collisions.getBoundingBox(
                TankPartNames.getIdx(TankPartNames.TURRET))
            gunBB = self.collisions.getBoundingBox(
                TankPartNames.getIdx(TankPartNames.GUN))
            hullTopY = desc.chassis.hullPosition[1] + hullBB[1][1]
            turretTopY = desc.chassis.hullPosition[
                1] + desc.hull.turretPositions[0][1] + turretBB[1][1]
            gunTopY = desc.chassis.hullPosition[1] + desc.hull.turretPositions[
                0][1] + desc.turret.gunPosition[1] + gunBB[1][1]
            gunLength = math.fabs(gunBB[1][2] - gunBB[0][2])
            height = max(hullTopY, max(turretTopY, gunTopY))
        return (height, gunLength)

    def computeVehicleLength(self):
        vehicleLength = 0.0
        if self.collisions is not None:
            hullBB = self.collisions.getBoundingBox(
                TankPartNames.getIdx(TankPartNames.HULL))
            vehicleLength = abs(hullBB[1][2] - hullBB[0][2])
        return vehicleLength

    def __reload(self, vDesc, vState, outfit):
        self.__loadState.unload()
        ComponentSystem.deactivate(self)
        self.tracks = None
        self.collisionObstaclesCollector = None
        self.tessellationCollisionSensor = None
        self.shadowManager = VehicleShadowManager()
        self.shadowManager.updatePlayerTarget(None)
        self.__outfit = outfit
        self.__startBuild(vDesc, vState)
        return

    def __startBuild(self, vDesc, vState):
        self.__curBuildInd += 1
        self.__vState = vState
        self.__resources = {}
        self.__vehicleStickers = None
        cfg = hangarCFG()
        if vState == 'undamaged':
            self.__currentEmblemsAlpha = cfg['emblems_alpha_undamaged']
            self.__isVehicleDestroyed = False
        else:
            self.__currentEmblemsAlpha = cfg['emblems_alpha_damaged']
            self.__isVehicleDestroyed = True
        self.__vDesc = vDesc
        resources = camouflages.getCamoPrereqs(self.__outfit, vDesc)
        splineDesc = vDesc.chassis.splineDesc
        if splineDesc is not None:
            resources.append(splineDesc.segmentModelLeft)
            resources.append(splineDesc.segmentModelRight)
            if splineDesc.leftDesc is not None:
                resources.append(splineDesc.leftDesc)
            if splineDesc.rightDesc is not None:
                resources.append(splineDesc.rightDesc)
            if splineDesc.segment2ModelLeft is not None:
                resources.append(splineDesc.segment2ModelLeft)
            if splineDesc.segment2ModelRight is not None:
                resources.append(splineDesc.segment2ModelRight)
        from vehicle_systems import model_assembler
        resources.append(
            model_assembler.prepareCompoundAssembler(
                self.__vDesc,
                ModelsSetParams(self.__outfit.modelsSet, self.__vState),
                self.__spaceId))
        g_eventBus.handleEvent(CameraRelatedEvents(
            CameraRelatedEvents.VEHICLE_LOADING,
            ctx={
                'started': True,
                'vEntityId': self.__vEntity.id
            }),
                               scope=EVENT_BUS_SCOPE.DEFAULT)
        cfg = hangarCFG()
        gunScale = Math.Vector3(1.0, 1.0, 1.1)
        capsuleScale = Math.Vector3(1.5, 1.5, 1.5)
        loadedGunScale = cfg.get('cam_capsule_gun_scale', gunScale)
        if loadedGunScale is not None:
            gunScale = loadedGunScale
        loadedCapsuleScale = cfg.get('cam_capsule_scale', capsuleScale)
        if loadedCapsuleScale is not None:
            capsuleScale = loadedCapsuleScale
        bspModels = ((TankPartNames.getIdx(TankPartNames.CHASSIS),
                      vDesc.chassis.hitTester.bspModelName),
                     (TankPartNames.getIdx(TankPartNames.HULL),
                      vDesc.hull.hitTester.bspModelName),
                     (TankPartNames.getIdx(TankPartNames.TURRET),
                      vDesc.turret.hitTester.bspModelName),
                     (TankPartNames.getIdx(TankPartNames.GUN),
                      vDesc.gun.hitTester.bspModelName),
                     (TankPartNames.getIdx(TankPartNames.GUN) + 1,
                      vDesc.hull.hitTester.bspModelName, capsuleScale),
                     (TankPartNames.getIdx(TankPartNames.GUN) + 2,
                      vDesc.turret.hitTester.bspModelName, capsuleScale),
                     (TankPartNames.getIdx(TankPartNames.GUN) + 3,
                      vDesc.gun.hitTester.bspModelName, gunScale))
        collisionAssembler = BigWorld.CollisionAssembler(
            bspModels, self.__spaceId)
        resources.append(collisionAssembler)
        physicalTracksBuilders = vDesc.chassis.physicalTracks
        for name, builder in physicalTracksBuilders.iteritems():
            resources.append(
                builder.createLoader('{0}PhysicalTrack'.format(name)))

        BigWorld.loadResourceListBG(
            tuple(resources),
            makeCallbackWeak(self.__onResourcesLoaded, self.__curBuildInd))
        return

    def __onResourcesLoaded(self, buildInd, resourceRefs):
        if not self.__vDesc:
            return
        if buildInd != self.__curBuildInd:
            return
        failedIDs = resourceRefs.failedIDs
        resources = self.__resources
        succesLoaded = True
        for resID, resource in resourceRefs.items():
            if resID not in failedIDs:
                resources[resID] = resource
            LOG_ERROR('Could not load %s' % resID)
            succesLoaded = False

        self.collisions = resourceRefs['collisionAssembler']
        if succesLoaded:
            self.__setupModel(buildInd)
        g_eventBus.handleEvent(CameraRelatedEvents(
            CameraRelatedEvents.VEHICLE_LOADING,
            ctx={
                'started': False,
                'vEntityId': self.__vEntity.id
            }),
                               scope=EVENT_BUS_SCOPE.DEFAULT)
        super(HangarVehicleAppearance, self).activate()

    def __onSettingsChanged(self, diff):
        if 'showMarksOnGun' in diff:
            self.__showMarksOnGun = not diff['showMarksOnGun']
            self.refresh()

    def _getActiveOutfit(self):
        if g_currentPreviewVehicle.isPresent(
        ) or not g_currentVehicle.isPresent():
            return self.itemsFactory.createOutfit()
        else:
            vehicle = g_currentVehicle.item
            if not vehicle:
                return None
            season = g_tankActiveCamouflage.get(vehicle.intCD,
                                                SeasonType.UNDEFINED)
            if season == SeasonType.UNDEFINED or not vehicle.hasOutfitWithItems(
                    season):
                season = vehicle.getAnyOutfitSeason()
            g_tankActiveCamouflage[vehicle.intCD] = season
            outfit = vehicle.getOutfit(season)
            if not outfit:
                outfit = self.itemsFactory.createOutfit()
            return outfit

    def __assembleModel(self):
        from vehicle_systems import model_assembler
        resources = self.__resources
        self.__vEntity.model = resources[self.__vDesc.name]
        if not self.__isVehicleDestroyed:
            self.__fashions = VehiclePartsTuple(
                BigWorld.WGVehicleFashion(False), BigWorld.WGBaseFashion(),
                BigWorld.WGBaseFashion(), BigWorld.WGBaseFashion())
            model_assembler.setupTracksFashion(self.__vDesc,
                                               self.__fashions.chassis)
            self.__vEntity.model.setupFashions(self.__fashions)
            self.__initMaterialHandlers()
            model_assembler.assembleCollisionObstaclesCollector(self, None)
            model_assembler.assembleTessellationCollisionSensor(self, None)
            self.wheelsAnimator = model_assembler.createWheelsAnimator(
                self.__vEntity.model, self.__vDesc, None)
            self.trackNodesAnimator = model_assembler.createTrackNodesAnimator(
                self.__vEntity.model, self.__vDesc, self.wheelsAnimator)
            chassisFashion = self.__fashions.chassis
            splineTracksImpl = model_assembler.setupSplineTracks(
                chassisFashion, self.__vDesc, self.__vEntity.model,
                self.__resources)
            model_assembler.assembleTracks(self.__resources, self.__vDesc,
                                           self, splineTracksImpl, True)
            self.updateCustomization(self.__outfit)
            dirtEnabled = BigWorld.WG_dirtEnabled(
            ) and 'HD' in self.__vDesc.type.tags
            if dirtEnabled:
                dirtHandlers = [
                    BigWorld.PyDirtHandler(
                        True,
                        self.__vEntity.model.node(
                            TankPartNames.CHASSIS).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        self.__vEntity.model.node(
                            TankPartNames.HULL).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        self.__vEntity.model.node(
                            TankPartNames.TURRET).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        self.__vEntity.model.node(
                            TankPartNames.GUN).position.y)
                ]
                modelHeight, _ = self.computeVehicleHeight()
                self.dirtComponent = Vehicular.DirtComponent(
                    dirtHandlers, modelHeight)
                for fashionIdx, _ in enumerate(TankPartNames.ALL):
                    self.__fashions[fashionIdx].addMaterialHandler(
                        dirtHandlers[fashionIdx])

                self.dirtComponent.setBase()
        else:
            self.__fashions = VehiclePartsTuple(BigWorld.WGBaseFashion(),
                                                BigWorld.WGBaseFashion(),
                                                BigWorld.WGBaseFashion(),
                                                BigWorld.WGBaseFashion())
            self.__vEntity.model.setupFashions(self.__fashions)
            self.wheelsAnimator = None
            self.trackNodesAnimator = None
            self.dirtComponent = None
        cfg = hangarCFG()
        turretYaw = self.__vDesc.gun.staticTurretYaw
        gunPitch = self.__vDesc.gun.staticPitch
        if not ('AT-SPG' in self.__vDesc.type.tags
                or 'SPG' in self.__vDesc.type.tags):
            if turretYaw is None:
                turretYaw = cfg['vehicle_turret_yaw']
                turretYawLimits = self.__vDesc.gun.turretYawLimits
                if turretYawLimits is not None:
                    turretYaw = mathUtils.clamp(turretYawLimits[0],
                                                turretYawLimits[1], turretYaw)
            if gunPitch is None:
                gunPitch = cfg['vehicle_gun_pitch']
                gunPitchLimits = self.__vDesc.gun.pitchLimits['absolute']
                gunPitch = mathUtils.clamp(gunPitchLimits[0],
                                           gunPitchLimits[1], gunPitch)
        else:
            if turretYaw is None:
                turretYaw = 0.0
            if gunPitch is None:
                gunPitch = 0.0
        turretYawMatrix = mathUtils.createRotationMatrix((turretYaw, 0.0, 0.0))
        self.__vEntity.model.node(TankPartNames.TURRET, turretYawMatrix)
        gunPitchMatrix = mathUtils.createRotationMatrix((0.0, gunPitch, 0.0))
        self.__vEntity.model.node(TankPartNames.GUN, gunPitchMatrix)
        return

    def __onItemsCacheSyncCompleted(self, updateReason, _):
        if updateReason == CACHE_SYNC_REASON.DOSSIER_RESYNC and self.__vehicleStickers is not None and self.__getThisVehicleDossierInsigniaRank(
        ) != self.__vehicleStickers.getCurrentInsigniaRank():
            self.refresh()
        return

    def __getThisVehicleDossierInsigniaRank(self):
        vehicleDossier = self.itemsCache.items.getVehicleDossier(
            self.__vDesc.type.compactDescr)
        return vehicleDossier.getRandomStats().getAchievement(
            MARK_ON_GUN_RECORD).getValue()

    def __setupEmblems(self, outfit):
        if self.__vehicleStickers is not None:
            self.__vehicleStickers.detach()
        insigniaRank = 0
        if self.__showMarksOnGun:
            insigniaRank = self.__getThisVehicleDossierInsigniaRank()
        self.__vehicleStickers = VehicleStickers.VehicleStickers(
            self.__vDesc, insigniaRank, outfit)
        self.__vehicleStickers.alpha = self.__currentEmblemsAlpha
        self.__vehicleStickers.attach(self.__vEntity.model,
                                      self.__isVehicleDestroyed, False)
        BigWorld.player().stats.get('clanDBID', self.__onClanDBIDRetrieved)
        return

    def __onClanDBIDRetrieved(self, _, clanID):
        self.__vehicleStickers.setClanID(clanID)

    def __setupModel(self, buildIdx):
        self.__assembleModel()
        cfg = hangarCFG()
        matrix = mathUtils.createSRTMatrix(
            Math.Vector3(cfg['v_scale'], cfg['v_scale'], cfg['v_scale']),
            Math.Vector3(self.__vEntity.yaw, self.__vEntity.pitch,
                         self.__vEntity.roll), self.__vEntity.position)
        self.__vEntity.model.matrix = matrix
        self.__doFinalSetup(buildIdx)
        self.__vEntity.typeDescriptor = self.__vDesc
        gunColBox = self.collisions.getBoundingBox(
            TankPartNames.getIdx(TankPartNames.GUN) + 3)
        center = 0.5 * (gunColBox[1] - gunColBox[0])
        gunoffset = Math.Matrix()
        gunoffset.setTranslate((0.0, 0.0, center.z + gunColBox[0].z))
        gunLink = mathUtils.MatrixProviders.product(
            gunoffset, self.__vEntity.model.node(TankPartNames.GUN))
        collisionData = ((TankPartNames.getIdx(TankPartNames.CHASSIS),
                          self.__vEntity.model.matrix),
                         (TankPartNames.getIdx(TankPartNames.HULL),
                          self.__vEntity.model.node(TankPartNames.HULL)),
                         (TankPartNames.getIdx(TankPartNames.TURRET),
                          self.__vEntity.model.node(TankPartNames.TURRET)),
                         (TankPartNames.getIdx(TankPartNames.GUN),
                          self.__vEntity.model.node(TankPartNames.GUN)))
        self.collisions.connect(self.__vEntity.id,
                                ColliderTypes.VEHICLE_COLLIDER, collisionData)
        collisionData = ((TankPartNames.getIdx(TankPartNames.GUN) + 1,
                          self.__vEntity.model.node(TankPartNames.HULL)),
                         (TankPartNames.getIdx(TankPartNames.GUN) + 2,
                          self.__vEntity.model.node(TankPartNames.TURRET)),
                         (TankPartNames.getIdx(TankPartNames.GUN) + 3,
                          gunLink))
        self.collisions.connect(self.__vEntity.id,
                                ColliderTypes.HANGAR_VEHICLE_COLLIDER,
                                collisionData)
        self.__reloadColliderType(self.__vEntity.state)
        self.__reloadShadowManagerTarget(self.__vEntity.state)

    def __handleEntityUpdated(self, event):
        ctx = event.ctx
        if ctx['entityId'] == self.__vEntity.id:
            self.__reloadColliderType(ctx['state'])
            self.__reloadShadowManagerTarget(ctx['state'])

    def __reloadColliderType(self, state):
        if not self.collisions:
            return
        if state != CameraMovementStates.FROM_OBJECT:
            colliderData = (self.collisions.getColliderID(),
                            (TankPartNames.getIdx(TankPartNames.GUN) + 1,
                             TankPartNames.getIdx(TankPartNames.GUN) + 2,
                             TankPartNames.getIdx(TankPartNames.GUN) + 3))
            BigWorld.appendCameraCollider(colliderData)
        else:
            BigWorld.removeCameraCollider(self.collisions.getColliderID())

    def __reloadShadowManagerTarget(self, state):
        if not self.shadowManager or not self.__vEntity.model:
            return
        else:
            if state == CameraMovementStates.ON_OBJECT:
                self.shadowManager.updatePlayerTarget(self.__vEntity.model)
            elif state == CameraMovementStates.MOVING_TO_OBJECT:
                self.shadowManager.updatePlayerTarget(None)
            return

    def __doFinalSetup(self, buildIdx):
        if buildIdx != self.__curBuildInd:
            return
        else:
            self.__loadState.load()
            if self.__onLoadedCallback is not None:
                self.__onLoadedCallback()
                self.__onLoadedCallback = None
            if self.__onLoadedAfterRefreshCallback is not None:
                self.__onLoadedAfterRefreshCallback()
                self.__onLoadedAfterRefreshCallback = None
            if self.__vDesc is not None and 'observer' in self.__vDesc.type.tags:
                self.__vEntity.model.visible = False
            return

    def getSlotPositions(self):
        slots = {
            area: {
                GUI_ITEM_TYPE.INSCRIPTION: [],
                GUI_ITEM_TYPE.EMBLEM: []
            }
            for area in Area.ALL
        }
        hullEmblemSlots = EmblemSlotHelper(
            self.__vDesc.hull.emblemSlots, Area.HULL,
            Math.Matrix(self.__vEntity.model.node(TankPartNames.HULL)))
        if self.__vDesc.turret.showEmblemsOnGun:
            turretEmblemSlots = EmblemSlotHelper(
                self.__vDesc.turret.emblemSlots, Area.GUN,
                Math.Matrix(self.__vEntity.model.node(TankPartNames.GUN)))
        else:
            turretEmblemSlots = EmblemSlotHelper(
                self.__vDesc.turret.emblemSlots, Area.TURRET,
                Math.Matrix(self.__vEntity.model.node(TankPartNames.TURRET)))
        for emblemSlotHelper in (hullEmblemSlots, turretEmblemSlots):
            for emblemSlot in emblemSlotHelper.tankAreaSlot:
                startPos = emblemSlot.rayStart
                endPos = emblemSlot.rayEnd
                normal = startPos - endPos
                normal.normalise()
                worldEmblemNormal = emblemSlotHelper.worldMatrix.applyVector(
                    normal)
                sub = endPos - startPos
                half = sub / 2.0
                midPos = startPos + half
                worldEmblemPos = emblemSlotHelper.worldMatrix.applyPoint(
                    midPos)
                container = slots[emblemSlotHelper.tankAreaId]
                if emblemSlot.type == 'inscription':
                    container[GUI_ITEM_TYPE.INSCRIPTION].append(
                        Anchor(worldEmblemPos, worldEmblemNormal))
                if emblemSlot.type == 'player':
                    container[GUI_ITEM_TYPE.EMBLEM].append(
                        Anchor(worldEmblemPos, worldEmblemNormal))

        return slots

    def getEmblemPos(self, onHull, emblemType, emblemIdx):
        if onHull:
            emblemsDesc = self.__vDesc.hull.emblemSlots
            worldMat = Math.Matrix(
                self.__vEntity.model.node(TankPartNames.HULL))
        else:
            if self.__vDesc.turret.showEmblemsOnGun:
                node = self.__vEntity.model.node(TankPartNames.GUN)
            else:
                node = self.__vEntity.model.node(TankPartNames.TURRET)
            emblemsDesc = self.__vDesc.turret.emblemSlots
            worldMat = Math.Matrix(node)
        desiredEmblems = [
            emblem for emblem in emblemsDesc if emblem.type == emblemType
        ]
        if emblemIdx >= len(desiredEmblems):
            return None
        else:
            emblem = desiredEmblems[emblemIdx]
            direction = emblem[1] - emblem[0]
            direction.normalise()
            startPos = emblem[0]
            endPos = emblem[1]
            sub = endPos - startPos
            half = sub / 2.0
            hitPos = startPos + half
            hitPos = worldMat.applyPoint(hitPos)
            upVecWorld = worldMat.applyVector(emblem[2])
            upVecWorld.normalise()
            if abs(direction.pitch - math.pi / 2) < 0.1:
                direction = Math.Vector3(0, -1, 0) + upVecWorld * 0.01
                direction.normalise()
            direction = self.__correctEmblemLookAgainstGun(
                hitPos, direction, upVecWorld, emblem)
            return EmblemPositionParams(hitPos, direction, emblem)

    def getCentralPointForArea(self, areaIdx):
        def _getBBCenter(tankPartName):
            partIdx = TankPartNames.getIdx(tankPartName)
            boundingBox = Math.Matrix(
                self.__vEntity.model.getBoundsForPart(partIdx))
            bbCenter = boundingBox.applyPoint((0.5, 0.5, 0.5))
            return bbCenter

        if areaIdx == ApplyArea.HULL:
            trackLeftUpFront = self.__vEntity.model.node(
                'HP_TrackUp_LFront').position
            trackRightUpRear = self.__vEntity.model.node(
                'HP_TrackUp_RRear').position
            position = (trackLeftUpFront + trackRightUpRear) / 2.0
            bbCenter = _getBBCenter(TankPartNames.HULL)
            turretJointPosition = self.__vEntity.model.node(
                'HP_turretJoint').position
            position.y = min(turretJointPosition.y, bbCenter.y)
        elif areaIdx == ApplyArea.TURRET:
            position = _getBBCenter(TankPartNames.TURRET)
            position.y = self.__vEntity.model.node('HP_gunJoint').position.y
        elif areaIdx == ApplyArea.GUN_2:
            position = self.__vEntity.model.node('HP_gunJoint').position
        elif areaIdx == ApplyArea.GUN:
            gunJointPos = self.__vEntity.model.node('HP_gunJoint').position
            gunFirePos = self.__vEntity.model.node('HP_gunFire').position
            position = (gunFirePos + gunJointPos) / 2.0
        else:
            position = _getBBCenter(TankPartNames.CHASSIS)
        return position

    def __getEmblemCorners(self, hitPos, dir, up, emblem):
        cfg = hangarCFG()
        size = emblem[3] * cfg['v_scale']
        m = Math.Matrix()
        m.lookAt(hitPos, dir, up)
        m.invert()
        result = (Math.Vector3(size * 0.5, size * 0.5, -0.25),
                  Math.Vector3(size * 0.5, -size * 0.5, -0.25),
                  Math.Vector3(-size * 0.5, -size * 0.5, -0.25),
                  Math.Vector3(-size * 0.5, size * 0.5, -0.25))
        return [m.applyPoint(vec) for vec in result]

    def __correctEmblemLookAgainstGun(self, hitPos, dir, up, emblem):
        checkDirWorld = dir * -10.0
        cornersWorld = self.__getEmblemCorners(hitPos, dir, up, emblem)
        result = self.collisions.collideShape(
            TankPartNames.getIdx(TankPartNames.GUN),
            (cornersWorld[0], cornersWorld[1], cornersWorld[2],
             cornersWorld[3]), checkDirWorld)
        if result < 0.0:
            return dir
        dirRot = Math.Matrix()
        angle = _HANGAR_UNDERGUN_EMBLEM_ANGLE_SHIFT
        turretMat = Math.Matrix(self.__vEntity.model.node(
            TankPartNames.TURRET))
        fromTurretToHit = hitPos - turretMat.translation
        gunDir = turretMat.applyVector(Math.Vector3(0, 0, 1))
        if Math.Vector3(0, 1, 0).dot(gunDir * fromTurretToHit) < 0:
            angle = -angle
        dirRot.setRotateY(angle)
        normRot = Math.Matrix()
        normRot.setRotateYPR((dir.yaw, dir.pitch, 0))
        dirRot.postMultiply(normRot)
        dir = dirRot.applyVector(Math.Vector3(0, 0, 1))
        return dir

    def __initMaterialHandlers(self):
        for fashionIdx, _ in enumerate(TankPartNames.ALL):
            camoHandler = self.__camoHandlers[
                fashionIdx] = BigWorld.PyCamoHandler()
            repaintHandler = self.__repaintHandlers[
                fashionIdx] = BigWorld.PyRepaintHandler()
            self.__fashions[fashionIdx].addMaterialHandler(camoHandler)
            self.__fashions[fashionIdx].addMaterialHandler(repaintHandler)

    def updateCustomization(self, outfit=None, callback=None):
        if self.__isVehicleDestroyed:
            return
        outfit = outfit or self.itemsFactory.createOutfit()
        if self.__outfit.modelsSet != outfit.modelsSet:
            self.refresh(outfit, callback)
            return
        self.__updateCamouflage(outfit)
        self.__updatePaint(outfit)
        self.__updateDecals(outfit)

    def __updatePaint(self, outfit):
        for fashionIdx, _ in enumerate(TankPartNames.ALL):
            repaint = camouflages.getRepaint(outfit, fashionIdx, self.__vDesc)
            self.__repaintHandlers[fashionIdx].setRepaintParams(repaint)

    def __updateCamouflage(self, outfit):
        for fashionIdx, descId in enumerate(TankPartNames.ALL):
            camo = camouflages.getCamo(outfit, fashionIdx, self.__vDesc,
                                       descId, self.__vState != 'undamaged')
            if camo:
                self.__fashions[fashionIdx].setCamouflage()
                self.__camoHandlers[fashionIdx].setCamoParams(camo)
            self.__fashions[fashionIdx].removeCamouflage()

    def __updateDecals(self, outfit):
        self.__setupEmblems(outfit)
예제 #7
0
class _VehicleAppearance(ComponentSystem):
    __ROOT_NODE_NAME = 'V'
    itemsCache = dependency.descriptor(IItemsCache)
    settingsCore = dependency.descriptor(ISettingsCore)
    wheelsAnimator = ComponentDescriptor()
    trackNodesAnimator = ComponentDescriptor()

    def __init__(self, spaceId, vEntityId, hangarSpace):
        ComponentSystem.__init__(self)
        self.__isLoaded = False
        self.__curBuildInd = 0
        self.__vDesc = None
        self.__vState = None
        self.__fashions = VehiclePartsTuple(None, None, None, None)
        self.__spaceId = spaceId
        self.__vEntityId = vEntityId
        self.__onLoadedCallback = None
        self.__emblemsAlpha = _CFG['emblems_alpha_undamaged']
        self.__vehicleStickers = None
        self.__isVehicleDestroyed = False
        self.__smCb = None
        self.__smRemoveCb = None
        self.__hangarSpace = weakref.proxy(hangarSpace)
        self.__removeHangarShadowMap()
        self.__showMarksOnGun = self.settingsCore.getSetting('showMarksOnGun')
        self.settingsCore.onSettingsChanged += self.__onSettingsChanged
        self.itemsCache.onSyncCompleted += self.__onItemsCacheSyncCompleted
        return

    def recreate(self, vDesc, vState, onVehicleLoadedCallback=None):
        ComponentSystem.deactivate(self)
        self.__onLoadedCallback = onVehicleLoadedCallback
        self.__isLoaded = False
        self.__startBuild(vDesc, vState)

    def refresh(self):
        if self.__isLoaded:
            self.__onLoadedCallback = None
            self.__isLoaded = False
            self.__startBuild(self.__vDesc, self.__vState)
        entity = BigWorld.entity(self.__vEntityId)
        if isinstance(entity, HangarVehicle):
            entity.releaseBspModels()
        return

    def destroy(self):
        ComponentSystem.deactivate(self)
        ComponentSystem.destroy(self)
        self.__onLoadedCallback = None
        self.__vDesc = None
        self.__vState = None
        self.__isLoaded = False
        self.__curBuildInd = 0
        self.__vEntityId = None
        if self.__smCb is not None:
            BigWorld.cancelCallback(self.__smCb)
            self.__smCb = None
        if self.__smRemoveCb is not None:
            BigWorld.cancelCallback(self.__smRemoveCb)
            self.__smRemoveCb = None
        self.settingsCore.onSettingsChanged -= self.__onSettingsChanged
        self.itemsCache.onSyncCompleted -= self.__onItemsCacheSyncCompleted
        return

    def isLoaded(self):
        return self.__isLoaded

    def __startBuild(self, vDesc, vState):
        self.__curBuildInd += 1
        self.__vDesc = vDesc
        self.__vState = vState
        self.__resources = {}
        self.__vehicleStickers = None
        camouflageResources = {
            'camouflageExclusionMask': vDesc.type.camouflage.exclusionMask
        }
        customization = items.vehicles.g_cache.customization(
            vDesc.type.customizationNationID)
        if customization is not None and vDesc.camouflages is not None:
            activeCamo = g_tankActiveCamouflage['historical'].get(
                vDesc.type.compactDescr)
            if activeCamo is None:
                activeCamo = g_tankActiveCamouflage.get(
                    vDesc.type.compactDescr, 0)
            camouflageID = vDesc.camouflages[activeCamo][0]
            camouflageDesc = customization['camouflages'].get(camouflageID)
            if camouflageDesc is not None:
                camouflageResources['camouflageTexture'] = camouflageDesc[
                    'texture']
        if vState == 'undamaged':
            self.__emblemsAlpha = _CFG['emblems_alpha_undamaged']
            self.__isVehicleDestroyed = False
        else:
            self.__emblemsAlpha = _CFG['emblems_alpha_damaged']
            self.__isVehicleDestroyed = True
        resources = camouflageResources.values()
        splineDesc = vDesc.chassis.splineDesc
        if splineDesc is not None:
            resources.append(splineDesc.segmentModelLeft)
            resources.append(splineDesc.segmentModelRight)
            resources.append(splineDesc.leftDesc)
            resources.append(splineDesc.rightDesc)
            resources.append(splineDesc.segment2ModelLeft)
            resources.append(splineDesc.segment2ModelRight)
        resources.append(
            model_assembler.prepareCompoundAssembler(self.__vDesc,
                                                     self.__vState,
                                                     self.__spaceId))
        BigWorld.loadResourceListBG(
            tuple(resources),
            makeCallbackWeak(self.__onResourcesLoaded, self.__curBuildInd))
        return

    def __onResourcesLoaded(self, buildInd, resourceRefs):
        if buildInd != self.__curBuildInd:
            return
        failedIDs = resourceRefs.failedIDs
        resources = self.__resources
        succesLoaded = True
        for resID, resource in resourceRefs.items():
            if resID not in failedIDs:
                resources[resID] = resource
            else:
                LOG_ERROR('Could not load %s' % resID)
                succesLoaded = False

        if succesLoaded:
            self.__setupModel(buildInd)
        super(_VehicleAppearance, self).activate()

    def __onSettingsChanged(self, diff):
        if 'showMarksOnGun' in diff:
            self.__showMarksOnGun = not diff['showMarksOnGun']
            self.refresh()
        elif 'dynamicFov' in diff or 'fov' in diff:
            if 'fov' in diff:
                staticFOV, dynamicFOVLow, dynamicFOVTop = diff['fov']
                defaultHorizontalFov = math.radians(dynamicFOVTop)

                def resetFov(value):
                    FovExtended.instance().defaultHorizontalFov = value

                BigWorld.callback(0.0, partial(resetFov, defaultHorizontalFov))
            self.__hangarSpace.updateCameraByMouseMove(0, 0, 0)

    def __assembleModel(self):
        resources = self.__resources
        self.__model = resources[self.__vDesc.name]
        self.__setupEmblems(self.__vDesc)
        if not self.__isVehicleDestroyed:
            self.__fashions = VehiclePartsTuple(
                BigWorld.WGVehicleFashion(False), None, None, None)
            model_assembler.setupTracksFashion(self.__fashions.chassis,
                                               self.__vDesc,
                                               self.__isVehicleDestroyed)
            self.__model.setupFashions(self.__fashions)
            chassisFashion = self.__fashions.chassis
            model_assembler.setupSplineTracks(chassisFashion, self.__vDesc,
                                              self.__model, self.__resources)
            self.wheelsAnimator = model_assembler.createWheelsAnimator(
                self.__model, self.__vDesc, None)
            self.trackNodesAnimator = model_assembler.createTrackNodesAnimator(
                self.__model, self.__vDesc, self.wheelsAnimator)
        else:
            self.__fashions = VehiclePartsTuple(None, None, None, None)
            self.wheelsAnimator = None
            self.trackNodesAnimator = None
        self.updateCamouflage()
        yaw = self.__vDesc.gun.staticTurretYaw
        pitch = self.__vDesc.gun.staticPitch
        if yaw is None:
            yaw = 0.0
        if pitch is None:
            pitch = 0.0
        gunMatrix = mathUtils.createRTMatrix(Math.Vector3(yaw, pitch, 0.0),
                                             (0.0, 0.0, 0.0))
        self.__model.node('gun', gunMatrix)
        return self.__model

    def __removeHangarShadowMap(self):
        if self.__smCb is not None:
            BigWorld.cancelCallback(self.__smCb)
            self.__smCb = None
        if BigWorld.spaceLoadStatus() < 1.0:
            self.__smRemoveCb = BigWorld.callback(0,
                                                  self.__removeHangarShadowMap)
            return
        else:
            self.__smRemoveCb = None
            self.__hangarSpace.modifyFakeShadowAsset(
                _CFG['shadow_empty_texture_name'])
            return

    VehicleProxy = namedtuple('VehicleProxy', 'typeDescriptor')

    def __setupHangarShadow(self):
        if self.__smCb is None:
            self.__setupHangarShadowMap()
        return

    def __setupHangarShadowMap(self):
        if self.__smRemoveCb is not None:
            BigWorld.cancelCallback(self.__smRemoveCb)
            self.__smRemoveCb = None
        if BigWorld.spaceLoadStatus() < 1.0:
            self.__smCb = BigWorld.callback(0.0, self.__setupHangarShadowMap)
            return
        else:
            self.__smCb = None
            if 'observer' in self.__vDesc.type.tags:
                self.__removeHangarShadowMap()
                return
            shadowMapTexFileName = self.__vDesc.hull.hangarShadowTexture
            if shadowMapTexFileName is None:
                shadowMapTexFileName = _CFG['shadow_default_texture_name']
            self.__hangarSpace.modifyFakeShadowAsset(shadowMapTexFileName)
            return

    def __onItemsCacheSyncCompleted(self, updateReason, invalidItems):
        if updateReason == CACHE_SYNC_REASON.DOSSIER_RESYNC and self.__vehicleStickers is not None and self.__getThisVehicleDossierInsigniaRank(
        ) != self.__vehicleStickers.getCurrentInsigniaRank():
            self.refresh()
        return

    def __getThisVehicleDossierInsigniaRank(self):
        vehicleDossier = self.itemsCache.items.getVehicleDossier(
            self.__vDesc.type.compactDescr)
        return vehicleDossier.getRandomStats().getAchievement(
            MARK_ON_GUN_RECORD).getValue()

    def __setupEmblems(self, vDesc):
        if self.__vehicleStickers is not None:
            self.__vehicleStickers.detach()
        insigniaRank = 0
        if self.__showMarksOnGun:
            insigniaRank = self.__getThisVehicleDossierInsigniaRank()
        self.__vehicleStickers = VehicleStickers.VehicleStickers(
            vDesc, insigniaRank)
        self.__vehicleStickers.alpha = self.__emblemsAlpha
        self.__vehicleStickers.attach(self.__model, self.__isVehicleDestroyed,
                                      False)
        BigWorld.player().stats.get('clanDBID', self.__onClanDBIDRetrieved)
        return

    def updateVehicleSticker(self, playerEmblems, playerInscriptions):
        initialEmblems = copy.deepcopy(self.__vDesc.playerEmblems)
        initialInscriptions = copy.deepcopy(self.__vDesc.playerInscriptions)
        for idx, (emblemId, startTime, duration) in enumerate(playerEmblems):
            self.__vDesc.setPlayerEmblem(idx, emblemId, startTime, duration)

        for idx, (inscriptionId, startTime, duration,
                  color) in enumerate(playerInscriptions):
            self.__vDesc.setPlayerInscription(idx, inscriptionId, startTime,
                                              duration, color)

        self.__setupEmblems(self.__vDesc)
        self.__vDesc.playerEmblems = initialEmblems
        self.__vDesc.playerInscriptions = initialInscriptions

    def __onClanDBIDRetrieved(self, _, clanID):
        if self.__vehicleStickers is not None:
            self.__vehicleStickers.setClanID(clanID)
        return

    def __setupModel(self, buildIdx):
        model = self.__assembleModel()
        matrix = mathUtils.createSRTMatrix(
            Math.Vector3(_CFG['v_scale'], _CFG['v_scale'], _CFG['v_scale']),
            _CFG['v_start_angles'], _CFG['v_start_pos'])
        model.matrix = matrix
        self.__doFinalSetup(buildIdx, model)
        entity = BigWorld.entity(self.__vEntityId)
        if isinstance(entity, HangarVehicle):
            entity.typeDescriptor = self.__vDesc

    def __doFinalSetup(self, buildIdx, model):
        if buildIdx != self.__curBuildInd:
            return
        else:
            entity = BigWorld.entity(self.__vEntityId)
            if entity:
                entity.model = model
                self.__isLoaded = True
                if isinstance(entity, HangarVehicle):
                    entity.canDoHitTest(True)
                if self.__onLoadedCallback is not None:
                    self.__onLoadedCallback()
                    self.__onLoadedCallback = None
                if self.__smCb is None:
                    self.__setupHangarShadowMap()
            if self.__vDesc is not None and 'observer' in self.__vDesc.type.tags:
                model.visible = False
            return

    def getEmblemPos(self, onHull, emblemType, emblemIdx):
        emblemsDesc = None
        hitTester = ModelHitTester()
        worldMat = None
        if onHull:
            hitTester.bspModelName = self.__vDesc.hull.models.undamaged
            emblemsDesc = self.__vDesc.hull.emblemSlots
            worldMat = Math.Matrix(self.__model.node(TankPartNames.HULL))
        else:
            if self.__vDesc.turret.showEmblemsOnGun:
                node = self.__model.node(TankPartNames.GUN)
                hitTester.bspModelName = self.__vDesc.gun.models.undamaged
            else:
                node = self.__model.node(TankPartNames.TURRET)
                hitTester.bspModelName = self.__vDesc.turret.models.undamaged
            emblemsDesc = self.__vDesc.turret.emblemSlots
            worldMat = Math.Matrix(node)
        desiredEmblems = [
            emblem for emblem in emblemsDesc if emblem.type == emblemType
        ]
        if emblemIdx >= len(desiredEmblems):
            return
        else:
            emblem = desiredEmblems[emblemIdx]
            dir = emblem[1] - emblem[0]
            dir.normalise()
            startPos = emblem[0] - dir * 5
            endPos = emblem[1] + dir * 5
            hitTester.loadBspModel()
            collideRes = hitTester.localHitTest(startPos, endPos)
            hitTester.releaseBspModel()
            if collideRes is not None:
                collideRes = sorted(collideRes,
                                    lambda t1, t2: cmp(t1[0], t2[0]))
                distanceFromStart, normal = collideRes[0][0], collideRes[0][1]
                hitPos = startPos + dir * distanceFromStart
                hitPos = worldMat.applyPoint(hitPos)
                dir = -worldMat.applyVector(normal)
                dir.normalise()
                upVecWorld = worldMat.applyVector(emblem[2])
                upVecWorld.normalise()
                if abs(dir.pitch - math.pi / 2) < 0.1:
                    dir = Math.Vector3(0, -1, 0) + upVecWorld * 0.01
                    dir.normalise()
                dir = self.__correctEmblemLookAgainstGun(
                    hitPos, dir, upVecWorld, emblem)
                return (hitPos, dir, emblem)
            return
            return

    def __getEmblemCorners(self, hitPos, dir, up, emblem):
        size = emblem[3] * _CFG['v_scale']
        m = Math.Matrix()
        m.lookAt(hitPos, dir, up)
        m.invert()
        result = (Math.Vector3(size * 0.5, size * 0.5, -0.25),
                  Math.Vector3(size * 0.5, -size * 0.5, -0.25),
                  Math.Vector3(-size * 0.5, -size * 0.5, -0.25),
                  Math.Vector3(-size * 0.5, size * 0.5, -0.25))
        return [m.applyPoint(vec) for vec in result]

    def __correctEmblemLookAgainstGun(self, hitPos, dir, up, emblem):
        hitTester = self.__vDesc.gun.hitTester
        hitTester.loadBspModel()
        toLocalGun = Math.Matrix(self.__model.node(TankPartNames.GUN))
        toLocalGun.invert()
        checkDirLocal = toLocalGun.applyVector(dir) * -10
        cornersLocal = self.__getEmblemCorners(hitPos, dir, up, emblem)
        cornersLocal = [toLocalGun.applyPoint(vec) for vec in cornersLocal]
        testResult = hitTester.localCollidesWithTriangle(
            (cornersLocal[0], cornersLocal[2], cornersLocal[1]), checkDirLocal)
        testResult = testResult or hitTester.localCollidesWithTriangle(
            (cornersLocal[0], cornersLocal[3], cornersLocal[2]), checkDirLocal)
        hitTester.releaseBspModel()
        if not testResult:
            return dir
        dirRot = Math.Matrix()
        angle = _HANGAR_UNDERGUN_EMBLEM_ANGLE_SHIFT
        turretMat = Math.Matrix(self.__model.node(TankPartNames.TURRET))
        fromTurretToHit = hitPos - turretMat.translation
        gunDir = turretMat.applyVector(Math.Vector3(0, 0, 1))
        if Math.Vector3(0, 1, 0).dot(gunDir * fromTurretToHit) < 0:
            angle = -angle
        dirRot.setRotateY(angle)
        normRot = Math.Matrix()
        normRot.setRotateYPR((dir.yaw, dir.pitch, 0))
        dirRot.postMultiply(normRot)
        dir = dirRot.applyVector(Math.Vector3(0, 0, 1))
        return dir

    def __getCurrentCamouflage(self):
        if self.__vDesc.camouflages is not None:
            activeCamo = g_tankActiveCamouflage['historical'].get(
                self.__vDesc.type.compactDescr)
            if activeCamo is None:
                activeCamo = g_tankActiveCamouflage.get(
                    self.__vDesc.type.compactDescr, 0)
            camouflageID = self.__vDesc.camouflages[activeCamo][0]
        if camouflageID is None:
            for camouflageData in self.__vDesc.camouflages:
                if camouflageData[0] is not None:
                    camouflageID = camouflageData[0]
                    break

        return camouflageID

    def initFashions(self):
        fashions = list(self.__fashions)
        for fashionIdx, descId in enumerate(TankPartNames.ALL):
            fashion = self.__fashions[fashionIdx]
            if fashion is None:
                fashions[fashionIdx] = BigWorld.WGBaseFashion()

        self.__fashions = fashions
        self.__model.setupFashions(self.__fashions)
        self.__fashions[0].activate()
        return

    def updateCamouflage(self, camouflageID=None):
        if camouflageID is None:
            camouflageID = self.__getCurrentCamouflage()
        self.initFashions()
        self.__fashions = camouflages.applyCamouflage(
            self.__vDesc, self.__fashions, self.__vState != 'undamaged',
            camouflageID)
        self.__fashions = camouflages.applyRepaint(self.__vDesc,
                                                   self.__fashions)
        return