def recreate(self, vDesc, vState, callback):
     ComponentSystem.deactivate(self)
     self.shadowManager = VehicleShadowManager()
     self.shadowManager.updatePlayerTarget(None)
     self.__onLoadedCallback = callback
     self.__outfit = self.__getActiveOutfit()
     self.__loadState.unload()
     self.__startBuild(vDesc, vState)
     return
Пример #2
0
 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 __reload(self, vDesc, vState, outfit):
     self.__clearModelAnimators()
     self.__loadState.unload()
     if self.fashion is not None:
         self.fashion.removePhysicalTracks()
     ScriptGameObject.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
Пример #4
0
 def __reload(self, vDesc, vState, outfit):
     self.__clearModelAnimators()
     self.__loadState.unload()
     if self.fashion is not None:
         self.fashion.removePhysicalTracks()
     if self.tracks is not None:
         self.tracks.reset()
     if self.__vEntity.model is not None and self.__vEntity.model is not None:
         self.shadowManager.unregisterCompoundModel(self.__vEntity.model)
     self.shadowManager = None
     self.undamagedStateChildren = []
     self.reset()
     self.shadowManager = VehicleShadowManager()
     self.shadowManager.updatePlayerTarget(None)
     if outfit.style and outfit.style.isProgression:
         outfit = self.__getStyleProgressionOutfitData(outfit)
     self.__outfit = outfit
     self.__startBuild(vDesc, vState)
     return
class HangarVehicleAppearance(ScriptGameObject):
    __ROOT_NODE_NAME = 'V'
    itemsCache = dependency.descriptor(IItemsCache)
    itemsFactory = dependency.descriptor(IGuiItemsFactory)
    settingsCore = dependency.descriptor(ISettingsCore)
    turretAndGunAngles = dependency.descriptor(ITurretAndGunAngles)
    customizationService = dependency.descriptor(ICustomizationService)
    wheelsAnimator = ComponentDescriptor()
    trackNodesAnimator = ComponentDescriptor()
    collisions = ComponentDescriptor()
    shadowManager = ComponentDescriptor()
    dirtComponent = ComponentDescriptor()
    c11nComponent = ComponentDescriptor()
    tracks = ComponentDescriptor()
    collisionObstaclesCollector = ComponentDescriptor()
    tessellationCollisionSensor = ComponentDescriptor()
    flagComponent = ComponentDescriptor()

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

    @property
    def id(self):
        return self.__vEntity.id

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

    @property
    def filter(self):
        return None

    isVehicleDestroyed = property(lambda self: self.__isVehicleDestroyed)

    def __init__(self, spaceId, vEntity):
        ScriptGameObject.__init__(self, vEntity.spaceID)
        self.__loadState = _LoadStateNotifier()
        self.__curBuildInd = 0
        self.__vDesc = None
        self.__vState = None
        size = len(TankPartNames.ALL)
        self.__fashions = VehiclePartsTuple(*([None] * size))
        self.__repaintHandlers = [None] * size
        self.__camoHandlers = [None] * size
        self.__projectionDecalsHandlers = [None] * size
        self.__projectionDecalsUpdater = 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.__staticTurretYaw = 0.0
        self.__staticGunPitch = 0.0
        self.__anchorsHelpers = None
        self.__anchorsParams = None
        self.__attachments = []
        self.__modelAnimators = []
        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)
        g_currentVehicle.onChanged += self.__onVehicleChanged
        return

    def recreate(self, vDesc, vState=None, callback=None, outfit=None):
        self.__onLoadedCallback = callback
        self.__reload(vDesc, vState or self.__vState, outfit
                      or self._getActiveOutfit(vDesc))

    def remove(self):
        self.__clearModelAnimators()
        self.__loadState.unload()
        self.__vDesc = None
        self.__vState = None
        self.__isVehicleDestroyed = False
        self.__vEntity.model = None
        if self.shadowManager is not None:
            self.shadowManager.updatePlayerTarget(None)
        if self.collisions:
            BigWorld.removeCameraCollider(self.collisions.getColliderID())
            self.collisions = None
        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):
        if self.fashion is not None:
            self.fashion.removePhysicalTracks()
        if self.shadowManager is not None:
            self.shadowManager.updatePlayerTarget(None)
        self.__clearModelAnimators()
        ScriptGameObject.deactivate(self)
        ScriptGameObject.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.turretRotator = None
        self.settingsCore.onSettingsChanged -= self.__onSettingsChanged
        self.itemsCache.onSyncCompleted -= self.__onItemsCacheSyncCompleted
        g_eventBus.removeListener(CameraRelatedEvents.CAMERA_ENTITY_UPDATED,
                                  self.__handleEntityUpdated)
        g_currentVehicle.onChanged -= self.__onVehicleChanged
        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 recreateRequired(self, newOutfit):
        shouldUpdateModelsSet = self.__outfit.modelsSet != newOutfit.modelsSet
        shouldUpdateAttachments = not self.__outfit.attachments.isEqual(
            newOutfit.attachments)
        return shouldUpdateModelsSet or shouldUpdateAttachments

    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 computeFullVehicleLength(self):
        hullBB = Math.Matrix(
            self.__vEntity.model.getBoundsForPart(TankPartIndexes.HULL))
        return hullBB.applyVector(Math.Vector3(0.0, 0.0, 1.0)).length

    def _getTurretYaw(self):
        return self.turretAndGunAngles.getTurretYaw()

    def _getGunPitch(self):
        return self.turretAndGunAngles.getGunPitch()

    def __reload(self, vDesc, vState, outfit):
        self.__clearModelAnimators()
        self.__loadState.unload()
        if self.fashion is not None:
            self.fashion.removePhysicalTracks()
        ScriptGameObject.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)
        if not self.__isVehicleDestroyed:
            self.__attachments = camouflages.getAttachments(
                self.__outfit, vDesc)
        modelsSet = self.__outfit.modelsSet
        splineDesc = vDesc.chassis.splineDesc
        if splineDesc is not None:
            resources.append(splineDesc.segmentModelLeft(modelsSet))
            resources.append(splineDesc.segmentModelRight(modelsSet))
            if splineDesc.leftDesc is not None:
                resources.append(splineDesc.leftDesc)
            if splineDesc.rightDesc is not None:
                resources.append(splineDesc.rightDesc)
            segment2ModelLeft = splineDesc.segment2ModelLeft(modelsSet)
            if segment2ModelLeft is not None:
                resources.append(segment2ModelLeft)
            segment2ModelRight = splineDesc.segment2ModelRight(modelsSet)
            if segment2ModelRight is not None:
                resources.append(segment2ModelRight)
        from vehicle_systems import model_assembler
        resources.append(
            model_assembler.prepareCompoundAssembler(
                self.__vDesc,
                ModelsSetParams(modelsSet, self.__vState, self.__attachments),
                self.__spaceId))
        g_eventBus.handleEvent(CameraRelatedEvents(
            CameraRelatedEvents.VEHICLE_LOADING,
            ctx={
                'started': True,
                'vEntityId': self.__vEntity.id,
                'intCD': self.__vDesc.type.compactDescr
            }),
                               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, builders in physicalTracksBuilders.iteritems():
            for index, builder in enumerate(builders):
                resources.append(
                    builder.createLoader(
                        self.__spaceId,
                        '{0}{1}PhysicalTrack'.format(name, index), modelsSet))

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

    def __onResourcesLoaded(self, buildInd, resourceRefs):
        if not self.__vDesc:
            self.__fireResourcesLoadedEvent()
            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
            _logger.error('Could not load %s', resID)
            succesLoaded = False

        if self.collisions:
            BigWorld.removeCameraCollider(self.collisions.getColliderID())
        self.collisions = resourceRefs['collisionAssembler']
        if succesLoaded:
            self.__setupModel(buildInd)
        self.turretRotator = SimpleTurretRotator(
            self.compoundModel, easingCls=math_utils.Easing.squareEasing)
        self.__applyAttachmentsVisibility()
        self.__fireResourcesLoadedEvent()
        super(HangarVehicleAppearance, self).activate()

    def __fireResourcesLoadedEvent(self):
        compDescr = self.__vDesc.type.compactDescr if self.__vDesc is not None else None
        g_eventBus.handleEvent(CameraRelatedEvents(
            CameraRelatedEvents.VEHICLE_LOADING,
            ctx={
                'started': False,
                'vEntityId': self.__vEntity.id,
                'intCD': compDescr
            }),
                               scope=EVENT_BUS_SCOPE.DEFAULT)
        return

    def __onAnimatorsLoaded(self, buildInd, outfit, resourceRefs):
        if not self.__vDesc:
            return
        if buildInd != self.__curBuildInd:
            return
        self.__clearModelAnimators()
        self.__modelAnimators = camouflages.getModelAnimators(
            outfit, self.__vDesc, self.__spaceId, resourceRefs,
            self.compoundModel)
        if not self.__isVehicleDestroyed:
            self.__modelAnimators.extend(
                camouflages.getAttachmentsAnimators(self.__attachments,
                                                    self.__spaceId,
                                                    resourceRefs,
                                                    self.compoundModel))
        from vehicle_systems import model_assembler
        model_assembler.assembleCustomLogicComponents(self, self.__attachments,
                                                      self.__modelAnimators)
        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.start()

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

    def _getActiveOutfit(self, vDesc):
        if g_currentPreviewVehicle.isPresent(
        ) and not g_currentPreviewVehicle.isHeroTank:
            vehicleCD = g_currentPreviewVehicle.item.descriptor.makeCompactDescr(
            )
            return self.customizationService.getEmptyOutfitWithNationalEmblems(
                vehicleCD=vehicleCD)
        elif not g_currentVehicle.isPresent():
            if vDesc is not None:
                vehicleCD = vDesc.makeCompactDescr()
                outfit = self.customizationService.getEmptyOutfitWithNationalEmblems(
                    vehicleCD=vehicleCD)
            else:
                _logger.error(
                    'Failed to get base vehicle outfit. VehicleDescriptor is None.'
                )
                outfit = self.itemsFactory.createOutfit()
            return outfit
        else:
            vehicle = g_currentVehicle.item
            season = g_tankActiveCamouflage.get(vehicle.intCD,
                                                SeasonType.UNDEFINED)
            if season == SeasonType.UNDEFINED:
                season = vehicle.getAnyOutfitSeason()
            g_tankActiveCamouflage[vehicle.intCD] = season
            outfit = vehicle.getOutfit(season)
            if not outfit:
                vehicleCD = vehicle.descriptor.makeCompactDescr()
                outfit = self.customizationService.getEmptyOutfitWithNationalEmblems(
                    vehicleCD=vehicleCD)
            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(),
                                                BigWorld.WGBaseFashion(),
                                                BigWorld.WGBaseFashion(),
                                                BigWorld.WGBaseFashion())
            model_assembler.setupTracksFashion(self.__vDesc,
                                               self.__fashions.chassis)
            self.__vEntity.model.setupFashions(self.__fashions)
            model_assembler.assembleCollisionObstaclesCollector(
                self, None, self.__vDesc,
                BigWorld.player().spaceID)
            model_assembler.assembleTessellationCollisionSensor(self, None)
            wheelsScroll = None
            wheelsSteering = None
            if self.__vDesc.chassis.generalWheelsAnimatorConfig is not None:
                scrollableWheelsCount = self.__vDesc.chassis.generalWheelsAnimatorConfig.getWheelsCount(
                )
                wheelsScroll = [(lambda: 0.0)
                                for _ in xrange(scrollableWheelsCount)]
                steerableWheelsCount = self.__vDesc.chassis.generalWheelsAnimatorConfig.getSteerableWheelsCount(
                )
                wheelsSteering = [(lambda: 0.0)
                                  for _ in xrange(steerableWheelsCount)]
            chassisFashion = self.__fashions.chassis
            splineTracksImpl = model_assembler.setupSplineTracks(
                chassisFashion, self.__vDesc, self.__vEntity.model,
                self.__resources, self.__outfit.modelsSet)
            self.wheelsAnimator = model_assembler.createWheelsAnimator(
                self, ColliderTypes.VEHICLE_COLLIDER, self.__vDesc, lambda: 0,
                wheelsScroll, wheelsSteering, splineTracksImpl)
            self.trackNodesAnimator = model_assembler.createTrackNodesAnimator(
                self, self.__vDesc)
            model_assembler.assembleTracks(self.__resources, self.__vDesc,
                                           self, splineTracksImpl, True)
            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 = self.createComponent(
                    Vehicular.DirtComponent, dirtHandlers, modelHeight)
                for fashionIdx, _ in enumerate(TankPartNames.ALL):
                    self.__fashions[fashionIdx].addMaterialHandler(
                        dirtHandlers[fashionIdx])

                self.dirtComponent.setBase()
            outfitData = camouflages.getOutfitData(
                self, self.__outfit, self.__vDesc,
                self.__vState != 'undamaged')
            self.c11nComponent = self.createComponent(
                Vehicular.C11nEditComponent, self.__fashions,
                self.compoundModel, outfitData)
            self.__updateDecals(self.__outfit)
            self.__updateSequences(self.__outfit)
        else:
            self.__fashions = VehiclePartsTuple(BigWorld.WGVehicleFashion(),
                                                BigWorld.WGBaseFashion(),
                                                BigWorld.WGBaseFashion(),
                                                BigWorld.WGBaseFashion())
            self.__vEntity.model.setupFashions(self.__fashions)
            self.wheelsAnimator = None
            self.trackNodesAnimator = None
            self.dirtComponent = None
            self.flagComponent = None
        self.__staticTurretYaw = self.__vDesc.gun.staticTurretYaw
        self.__staticGunPitch = self.__vDesc.gun.staticPitch
        if not ('AT-SPG' in self.__vDesc.type.tags
                or 'SPG' in self.__vDesc.type.tags):
            if self.__staticTurretYaw is None:
                self.__staticTurretYaw = self._getTurretYaw()
                turretYawLimits = self.__vDesc.gun.turretYawLimits
                if turretYawLimits is not None:
                    self.__staticTurretYaw = math_utils.clamp(
                        turretYawLimits[0], turretYawLimits[1],
                        self.__staticTurretYaw)
            if self.__staticGunPitch is None:
                self.__staticGunPitch = self._getGunPitch()
                gunPitchLimits = self.__vDesc.gun.pitchLimits['absolute']
                self.__staticGunPitch = math_utils.clamp(
                    gunPitchLimits[0], gunPitchLimits[1],
                    self.__staticGunPitch)
        else:
            if self.__staticTurretYaw is None:
                self.__staticTurretYaw = 0.0
            if self.__staticGunPitch is None:
                self.__staticGunPitch = 0.0
        turretYawMatrix = math_utils.createRotationMatrix(
            (self.__staticTurretYaw, 0.0, 0.0))
        self.__vEntity.model.node(TankPartNames.TURRET, turretYawMatrix)
        gunPitchMatrix = math_utils.createRotationMatrix(
            (0.0, self.__staticGunPitch, 0.0))
        self.__setGunMatrix(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):
        if self.__vDesc:
            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()
        matrix = math_utils.createSRTMatrix(
            Math.Vector3(1.0, 1.0, 1.0),
            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))
        gunNode = self.__getGunNode()
        gunLink = math_utils.MatrixProviders.product(gunoffset, gunNode)
        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), gunNode))
        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 __applyAttachmentsVisibility(self):
        if self.compoundModel is None:
            return False
        else:
            partHandleNotFoundErrorCode = 4294967295L
            for attachment in self.__attachments:
                partNode = self.compoundModel.node(attachment.partNodeAlias)
                if partNode is None:
                    _logger.error('Attachment node "%s" is not found.',
                                  attachment.partNodeAlias)
                    continue
                partId = self.compoundModel.findPartHandleByNode(partNode)
                if partId == partHandleNotFoundErrorCode:
                    _logger.error('Part handle is not found, see node "%s"',
                                  attachment.partNodeAlias)
                    continue
                if not attachment.initialVisibility:
                    self.compoundModel.setPartVisible(partId, False)

            return True

    def getAnchorParams(self, slotId, areaId, regionIdx):
        if self.__anchorsParams is None:
            self.__initAnchorsParams()
        anchorParams = self.__anchorsParams.get(slotId,
                                                {}).get(areaId,
                                                        {}).get(regionIdx)
        return anchorParams

    def updateAnchorsParams(self, tankPartsToUpdate=TankPartIndexes.ALL):
        if self.__anchorsHelpers is None or self.__anchorsParams is None:
            return
        else:
            self.__updateAnchorsParams(tankPartsToUpdate)
            return

    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 updateCustomization(self, outfit=None, callback=None):
        if self.__isVehicleDestroyed:
            return
        vehicleCD = g_currentVehicle.item.descriptor.makeCompactDescr()
        outfit = outfit or self.customizationService.getEmptyOutfitWithNationalEmblems(
            vehicleCD=vehicleCD)
        if self.recreateRequired(outfit):
            self.refresh(outfit, callback)
            return
        self.__updateCamouflage(outfit)
        self.__updatePaint(outfit)
        self.__updateDecals(outfit)
        self.__updateProjectionDecals(outfit)
        self.__updateSequences(outfit)

    def rotateTurretForAnchor(self, anchorId):
        if self.compoundModel is None or self.__vDesc is None:
            return False
        else:
            defaultYaw = self._getTurretYaw()
            turretYaw = self.__getTurretYawForAnchor(anchorId, defaultYaw)
            self.turretRotator.start(turretYaw,
                                     rotationTime=EASING_TRANSITION_DURATION)
            return

    def rotateGunToDefault(self):
        if self.compoundModel is None:
            return False
        else:
            localGunMatrix = self.__getGunNode().local
            currentGunPitch = localGunMatrix.pitch
            gunPitchAngle = self._getGunPitch()
            if abs(currentGunPitch - gunPitchAngle) < 0.0001:
                return False
            gunPitchMatrix = math_utils.createRotationMatrix(
                (0.0, gunPitchAngle, 0.0))
            self.__setGunMatrix(gunPitchMatrix)
            return True

    def __getAnchorHelperById(self, anchorId):
        if anchorId.slotType not in self.__anchorsHelpers:
            return None
        else:
            slotTypeAnchorHelpers = self.__anchorsHelpers[anchorId.slotType]
            if anchorId.areaId not in slotTypeAnchorHelpers:
                return None
            areaAnchorHelpers = slotTypeAnchorHelpers[anchorId.areaId]
            return None if anchorId.regionIdx not in areaAnchorHelpers else areaAnchorHelpers[
                anchorId.regionIdx]

    def __updateAnchorHelperWithId(self, anchorId, newAnchorHelper):
        self.__anchorsHelpers[anchorId.slotType][anchorId.areaId][
            anchorId.regionIdx] = newAnchorHelper

    def __getTurretYawForAnchor(self, anchorId, defaultYaw):
        turretYaw = defaultYaw
        if anchorId is not None and hasTurretRotator(self.__vDesc):
            anchorHelper = self.__getAnchorHelperById(anchorId)
            if anchorHelper is not None:
                if anchorHelper.turretYaw is not None:
                    turretYaw = anchorHelper.turretYaw
                else:
                    if anchorHelper.attachedPartIdx == TankPartIndexes.HULL:
                        needsCorrection = anchorId.slotType in (
                            GUI_ITEM_TYPE.EMBLEM, GUI_ITEM_TYPE.INSCRIPTION
                        ) or anchorId.slotType == GUI_ITEM_TYPE.PROJECTION_DECAL and anchorHelper.descriptor.showOn == ApplyArea.HULL
                        if needsCorrection:
                            turretYaw = self.__correctTurretYaw(
                                anchorHelper, defaultYaw)
                    anchorHelper = AnchorHelper(anchorHelper.location,
                                                anchorHelper.descriptor,
                                                turretYaw,
                                                anchorHelper.partIdx,
                                                anchorHelper.attachedPartIdx)
                    self.__updateAnchorHelperWithId(anchorId, anchorHelper)
        turretYawLimits = self.__vDesc.gun.turretYawLimits
        if turretYawLimits is not None:
            turretYaw = math_utils.clamp(turretYawLimits[0],
                                         turretYawLimits[1], turretYaw)
        return turretYaw

    def __updatePaint(self, outfit):
        for fashionIdx, _ in enumerate(TankPartNames.ALL):
            repaint = camouflages.getRepaint(outfit, fashionIdx, self.__vDesc)
            self.c11nComponent.setPartPaint(fashionIdx, repaint)

    def __updateCamouflage(self, outfit):
        for fashionIdx, descId in enumerate(TankPartNames.ALL):
            camo = camouflages.getCamo(self, outfit, fashionIdx, self.__vDesc,
                                       descId, self.__vState != 'undamaged')
            self.c11nComponent.setPartCamo(fashionIdx, camo)

    def __updateDecals(self, outfit):
        self.__setupEmblems(outfit)

    def __updateProjectionDecals(self, outfit):
        decals = camouflages.getGenericProjectionDecals(outfit, self.__vDesc)
        self.c11nComponent.setDecals(decals)

    def __updateSequences(self, outfit):
        resources = camouflages.getModelAnimatorsPrereqs(
            outfit, self.__spaceId)
        resources.extend(
            camouflages.getAttachmentsAnimatorsPrereqs(self.__attachments,
                                                       self.__spaceId))
        if not resources:
            self.__clearModelAnimators()
            return
        BigWorld.loadResourceListBG(
            tuple(resources),
            makeCallbackWeak(self.__onAnimatorsLoaded, self.__curBuildInd,
                             outfit))

    def __clearModelAnimators(self):
        self.flagComponent = None
        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.stop()

        self.__modelAnimators = []
        return

    def __onVehicleChanged(self):
        self.__anchorsParams = None
        self.__anchorsHelpers = None
        return

    def __initAnchorsParams(self):
        self.__anchorsParams = {
            cType: {area: {}
                    for area in Area.ALL}
            for cType in SLOT_TYPES
        }
        if self.__anchorsHelpers is None:
            self.__initAnchorsHelpers()
        self.__updateAnchorsParams(TankPartIndexes.ALL)
        return

    def __updateAnchorsParams(self, tankPartsToUpdate):
        tankPartsMatrices = {}
        for tankPartIdx in TankPartIndexes.ALL:
            tankPartName = TankPartIndexes.getName(tankPartIdx)
            tankPartsMatrices[tankPartIdx] = Math.Matrix(
                self.__vEntity.model.node(tankPartName))

        for slotType in SLOT_TYPES:
            for areaId in Area.ALL:
                anchorHelpers = self.__anchorsHelpers[slotType][areaId]
                for regionIdx, anchorHelper in anchorHelpers.iteritems():
                    attachedPartIdx = anchorHelper.attachedPartIdx
                    if attachedPartIdx not in tankPartsToUpdate:
                        continue
                    anchorLocationWS = self.__getAnchorLocationWS(
                        anchorHelper.location, anchorHelper.partIdx)
                    self.__anchorsParams[slotType][areaId][
                        regionIdx] = AnchorParams(
                            anchorLocationWS, anchorHelper.descriptor,
                            AnchorId(slotType, areaId, regionIdx))

    def __initAnchorsHelpers(self):
        anchorsHelpers = {
            cType: {area: {}
                    for area in Area.ALL}
            for cType in SLOT_TYPES
        }
        for slotType in SLOT_TYPES:
            for areaId in Area.ALL:
                for regionIdx, anchor in g_currentVehicle.item.getAnchors(
                        slotType, areaId):
                    if isinstance(anchor, EmblemSlot):
                        getAnchorHelper = self.__getEmblemAnchorHelper
                    elif isinstance(anchor, BaseCustomizationSlot):
                        getAnchorHelper = self.__getAnchorHelper
                    else:
                        continue
                    anchorsHelpers[slotType][areaId][
                        regionIdx] = getAnchorHelper(anchor)

        self.__anchorsHelpers = anchorsHelpers

    def __getEmblemAnchorHelper(self, anchor):
        startPos = anchor.rayStart
        endPos = anchor.rayEnd
        normal = startPos - endPos
        normal.normalise()
        up = normal * (anchor.descriptor.rayUp * normal)
        up.normalise()
        position = startPos + (endPos - startPos) * 0.5
        anchorLocation = AnchorLocation(position, normal, up)
        partIdx = anchor.areaId
        attachedPartIdx = self.__getAttachedPartIdx(position, normal, partIdx)
        return AnchorHelper(anchorLocation, anchor.descriptor, None, partIdx,
                            attachedPartIdx)

    def __getAnchorHelper(self, anchor):
        slotType = ANCHOR_TYPE_TO_SLOT_TYPE_MAP[anchor.descriptor.type]
        if slotType in (GUI_ITEM_TYPE.MODIFICATION, GUI_ITEM_TYPE.STYLE):
            hullAABB = self.collisions.getBoundingBox(TankPartIndexes.HULL)
            position = Math.Vector3((hullAABB[1].x + hullAABB[0].x) / 2.0,
                                    hullAABB[1].y / 2.0,
                                    (hullAABB[1].z + hullAABB[0].z) / 2.0)
            partIdx = TankPartIndexes.HULL
        else:
            position = anchor.anchorPosition
            partIdx = anchor.areaId
        normal = anchor.anchorDirection
        normal.normalise()
        if slotType == GUI_ITEM_TYPE.PROJECTION_DECAL:
            ypr = anchor.descriptor.rotation
            rotationMatrix = Math.Matrix()
            rotationMatrix.setRotateYPR((ypr.y, ypr.x, ypr.z))
            up = rotationMatrix.applyVector((0, 0, -1))
        else:
            up = normal * (Math.Vector3(0, 1, 0) * normal)
        anchorLocation = AnchorLocation(position, normal, up)
        attachedPartIdx = self.__getAttachedPartIdx(position, normal, partIdx)
        return AnchorHelper(anchorLocation, anchor.descriptor, None, partIdx,
                            attachedPartIdx)

    def __getAttachedPartIdx(self, position, normal, tankPartIdx):
        partMatrix = Math.Matrix(
            self.__vEntity.model.node(TankPartIndexes.getName(tankPartIdx)))
        startPos = position + normal * 0.1
        endPos = position - normal * 0.6
        startPos = partMatrix.applyPoint(startPos)
        endPos = partMatrix.applyPoint(endPos)
        collisions = self.collisions.collideAllWorld(startPos, endPos)
        if collisions is not None:
            for collision in collisions:
                partIdx = collision[3]
                if partIdx in TankPartIndexes.ALL:
                    return partIdx

        return tankPartIdx

    def __correctTurretYaw(self, anchorHelper, defaultYaw):
        if not _SHOULD_CHECK_DECAL_UNDER_GUN:
            return defaultYaw
        else:
            partMatrix = Math.Matrix(
                self.__vEntity.model.node(
                    TankPartIndexes.getName(anchorHelper.partIdx)))
            turretMat = Math.Matrix(
                self.compoundModel.node(TankPartNames.TURRET))
            transformMatrix = math_utils.createRTMatrix(
                (turretMat.yaw, partMatrix.pitch, partMatrix.roll),
                partMatrix.translation)
            anchorLocationWS = self.__applyToAnchorLocation(
                anchorHelper.location, transformMatrix)
            position = anchorLocationWS.position
            direction = anchorLocationWS.normal
            up = anchorLocationWS.up
            fromTurretToHit = position - turretMat.translation
            if fromTurretToHit.dot(turretMat.applyVector((0, 0, 1))) < 0:
                return defaultYaw
            checkDirWorld = direction * 10.0
            cornersWorldSpace = self.__getDecalCorners(position, direction, up,
                                                       anchorHelper.descriptor)
            if cornersWorldSpace is None:
                return defaultYaw
            slotType = ANCHOR_TYPE_TO_SLOT_TYPE_MAP[
                anchorHelper.descriptor.type]
            if slotType == GUI_ITEM_TYPE.PROJECTION_DECAL:
                turretLeftDir = turretMat.applyVector((1, 0, 0))
                turretLeftDir.normalise()
                gunJoin = self.__vEntity.model.node('HP_gunJoint')
                fromGunJointToAnchor = gunJoin.position - position
                decalDiags = (cornersWorldSpace[0] - cornersWorldSpace[2],
                              cornersWorldSpace[1] - cornersWorldSpace[3])
                fromGunToHit = abs(fromGunJointToAnchor.dot(turretLeftDir))
                halfDecalWidth = max((abs(decalDiag.dot(turretLeftDir))
                                      for decalDiag in decalDiags)) * 0.5
                if fromGunToHit > halfDecalWidth * _PROJECTION_DECAL_OVERLAPPING_FACTOR:
                    return defaultYaw
            result = self.collisions.collideShape(TankPartIndexes.GUN,
                                                  cornersWorldSpace,
                                                  checkDirWorld)
            if result < 0.0:
                return defaultYaw
            turretYaw = _HANGAR_TURRET_SHIFT
            gunDir = turretMat.applyVector(Math.Vector3(0, 0, 1))
            if Math.Vector3(0, 1, 0).dot(gunDir * fromTurretToHit) > 0.0:
                turretYaw = -turretYaw
            return turretYaw

    def __getDecalCorners(self, position, direction, up, slotDescriptor):
        slotType = ANCHOR_TYPE_TO_SLOT_TYPE_MAP[slotDescriptor.type]
        if slotType == GUI_ITEM_TYPE.PROJECTION_DECAL:
            width = slotDescriptor.scale[0]
            aspect = getProgectionDecalAspect(slotDescriptor)
            height = slotDescriptor.scale[2] * aspect
        else:
            width = slotDescriptor.size
            aspect = SLOT_ASPECT_RATIO.get(slotType)
            if aspect is not None:
                height = width * aspect
            else:
                return
        transformMatrix = Math.Matrix()
        transformMatrix.lookAt(position, direction, up)
        transformMatrix.invert()
        result = (Math.Vector3(width * 0.5, height * 0.5,
                               0), Math.Vector3(width * 0.5, -height * 0.5, 0),
                  Math.Vector3(-width * 0.5, -height * 0.5,
                               0), Math.Vector3(-width * 0.5, height * 0.5, 0))
        return tuple((transformMatrix.applyPoint(vec) for vec in result))

    def __applyToAnchorLocation(self, anchorLocation, transform):
        position = transform.applyPoint(anchorLocation.position)
        normal = transform.applyVector(anchorLocation.normal)
        up = transform.applyVector(anchorLocation.up)
        if abs(normal.pitch - math.pi / 2) < 0.1:
            normal = Math.Vector3(0, -1, 0) + up * 0.01
            normal.normalise()
        return AnchorLocation(position, normal, up)

    def __getAnchorLocationWS(self, anchorLocation, partIdx):
        partMatrix = Math.Matrix(
            self.__vEntity.model.node(TankPartIndexes.getName(partIdx)))
        return self.__applyToAnchorLocation(anchorLocation, partMatrix)

    def __getGunNode(self):
        gunNode = self.compoundModel.node(TankNodeNames.GUN_INCLINATION)
        if gunNode is None:
            gunNode = self.compoundModel.node(TankPartNames.GUN)
        return gunNode

    def __setGunMatrix(self, gunMatrix):
        gunNode = self.__getGunNode()
        gunNode.local = gunMatrix
class CommonTankAppearance(ScriptGameObject):
    compoundModel = property(lambda self: self._compoundModel)
    boundEffects = property(lambda self: self.__boundEffects)
    fashions = property(lambda self: self.__fashions)
    fashion = property(lambda self: self.fashions.chassis)
    typeDescriptor = property(lambda self: self.__typeDesc if self._vehicle is
                              None else self._vehicle.typeDescriptor)
    id = property(lambda self: self.__vID)
    isAlive = property(lambda self: self.__isAlive)
    isObserver = property(lambda self: self.__isObserver)
    outfit = property(lambda self: self.__outfit)
    renderMode = property(lambda self: self.__renderMode)

    def _setFashions(self, fashions, isTurretDetached=False):
        if IS_EDITOR and self.__fashions:
            for fashion in self.__fashions:
                if fashion:
                    fashion.disowned()

        self.__fashions = fashions
        if isTurretDetached:
            self.compoundModel.setupFashions((fashions.chassis, fashions.hull))
        else:
            self.compoundModel.setupFashions(fashions)

    def _setOutfit(self, outfitCD):
        self.__outfit = self._prepareOutfit(outfitCD)

    terrainMatKind = property(lambda self: self.__currTerrainMatKind)
    terrainGroundType = property(lambda self: self.__currTerrainGroundType)
    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)
    modelsSetParams = property(
        lambda self: ModelsSetParams(self.outfit.modelsSet, self.damageState.
                                     modelState, self.__attachments))
    splineTracks = property(lambda self: self._splineTracks)
    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)
    trackScrollController = property(lambda self: self.__trackScrollCtl)
    wheelsState = property(lambda self: 0)
    burnoutLevel = property(lambda self: 0.0)
    wheelsGameObject = property(lambda self: self.__wheelsGameObject)
    filterRetrievers = property(lambda self: self.__filterRetrievers)
    filterRetrieverGameObjects = property(
        lambda self: self.__filterRetrieverGameObjects)
    allLodCalculators = property(lambda self: self.__allLodCalculators)
    transmissionSlip = property(lambda self: self._commonSlip)
    transmissionScroll = property(lambda self: self._commonScroll)
    vehicleStickers = property(lambda self: self._vehicleStickers)
    isTurretDetached = property(lambda self: self._isTurretDetached)
    _weaponEnergy = property(lambda self: self.__weaponEnergy)
    filter = AutoProperty()
    areaTriggerTarget = ComponentDescriptor()
    burnoutProcessor = ComponentDescriptor()
    c11nComponent = ComponentDescriptor()
    collisionObstaclesCollector = ComponentDescriptor()
    collisions = ComponentDescriptor()
    crashedTracksController = ComponentDescriptor()
    customEffectManager = ComponentDescriptor()
    detailedEngineState = ComponentDescriptor()
    dirtComponent = ComponentDescriptor()
    engineAudition = ComponentDescriptor()
    flyingInfoProvider = ComponentDescriptor()
    frictionAudition = ComponentDescriptor()
    gearbox = ComponentDescriptor()
    gunLinkedNodesAnimator = ComponentDescriptor()
    gunRecoil = ComponentDescriptor()
    gunRotatorAudition = ComponentDescriptor()
    hullAimingController = ComponentDescriptor()
    leveredSuspension = ComponentDescriptor()
    lodCalculator = ComponentDescriptor()
    shadowManager = ComponentDescriptor()
    siegeEffects = ComponentDescriptor()
    suspension = ComponentDescriptor()
    suspensionSound = ComponentDescriptor()
    swingingAnimator = ComponentDescriptor()
    terrainMatKindSensor = ComponentDescriptor()
    tessellationCollisionSensor = ComponentDescriptor()
    trackNodesAnimator = ComponentDescriptor()
    tracks = ComponentDescriptor()
    transform = ComponentDescriptor()
    vehicleTraces = ComponentDescriptor()
    waterSensor = ComponentDescriptor()
    wheelsAnimator = ComponentDescriptor()
    flagComponent = ComponentDescriptor()

    def __init__(self, spaceID):
        ScriptGameObject.__init__(self, spaceID, CgfTankNodes.TANK_ROOT)
        self._vehicle = None
        self.__wheelsGameObject = ScriptGameObject(spaceID, 'Tank.Wheels.Root')
        self.__filter = None
        self.__typeDesc = None
        self.crashedTracksController = None
        self.__currentDamageState = VehicleDamageState()
        self.__currTerrainMatKind = [-1] * MATKIND_COUNT
        self.__currTerrainGroundType = [-1] * MATKIND_COUNT
        self.__terrainEffectMaterialNames = [''] * MATKIND_COUNT
        self._chassisDecal = VehicleDecal(self)
        self.__splodge = None
        self.__boundEffects = None
        self._splineTracks = None
        self.flyingInfoProvider = self.createComponent(
            Vehicular.FlyingInfoProvider)
        self.__trackScrollCtl = BigWorld.PyTrackScroll()
        self.__trackScrollCtl.setFlyingInfo(
            DataLinks.createBoolLink(self.flyingInfoProvider,
                                     'isLeftSideFlying'),
            DataLinks.createBoolLink(self.flyingInfoProvider,
                                     'isRightSideFlying'))
        self.__weaponEnergy = 0.0
        self.__outfit = None
        self.__systemStarted = False
        self.__isAlive = True
        self._isTurretDetached = False
        self.__isObserver = False
        self.__attachments = []
        self.__modelAnimators = []
        self.turretMatrix = None
        self.gunMatrix = None
        self.__allLodCalculators = []
        self._commonScroll = 0.0
        self._commonSlip = 0.0
        self._compoundModel = None
        self.__fashions = None
        self.__filterRetrievers = []
        self.__filterRetrieverGameObjects = []
        self._vehicleStickers = None
        self._vehicleInfo = {}
        self.__vID = 0
        self.__renderMode = None
        self.__frameTimestamp = 0
        self.__periodicTimerID = None
        self.undamagedStateChildren = []
        self.createComponent(VehicleAppearanceComponent, self)
        self._loadingQueue = []
        return

    def prerequisites(self,
                      typeDescriptor,
                      vID,
                      health,
                      isCrewActive,
                      isTurretDetached,
                      outfitCD,
                      renderMode=None):
        self.damageState.update(health, isCrewActive, False)
        self.__typeDesc = typeDescriptor
        self.__vID = vID
        self._isTurretDetached = isTurretDetached
        self.__updateModelStatus()
        self.__outfit = self._prepareOutfit(outfitCD)
        if self.damageState.isCurrentModelUndamaged:
            self.__attachments = camouflages.getAttachments(
                self.outfit, self.typeDescriptor)
        self.__renderMode = renderMode
        prereqs = self.typeDescriptor.prerequisites(True)
        prereqs.extend(
            camouflages.getCamoPrereqs(self.outfit, self.typeDescriptor))
        prereqs.extend(
            camouflages.getModelAnimatorsPrereqs(self.outfit, self.spaceID))
        prereqs.extend(
            camouflages.getAttachmentsAnimatorsPrereqs(self.__attachments,
                                                       self.spaceID))
        splineDesc = self.typeDescriptor.chassis.splineDesc
        modelsSet = self.outfit.modelsSet
        if IS_EDITOR:
            modelsSet = self.currentModelsSet
        if splineDesc is not None:
            for _, trackDesc in splineDesc.trackPairs.iteritems():
                prereqs += trackDesc.prerequisites(modelsSet)

        modelsSetParams = self.modelsSetParams
        compoundAssembler = model_assembler.prepareCompoundAssembler(
            self.typeDescriptor,
            modelsSetParams,
            self.spaceID,
            self.isTurretDetached,
            renderMode=self.renderMode)
        prereqs.append(compoundAssembler)
        if renderMode == TankRenderMode.OVERLAY_COLLISION:
            self.damageState.update(0, isCrewActive, False)
        collisionAssembler = model_assembler.prepareCollisionAssembler(
            self.typeDescriptor, self.isTurretDetached, self.spaceID)
        prereqs.append(collisionAssembler)
        skin = modelsSetParams.skin
        if IS_EDITOR:
            skin = self.currentModelsSet
        physicalTracksBuilders = self.typeDescriptor.chassis.physicalTracks
        for name, builders in physicalTracksBuilders.iteritems():
            for index, builder in enumerate(builders):
                prereqs.append(
                    builder.createLoader(
                        self.spaceID,
                        '{0}{1}PhysicalTrack'.format(name, index), skin))

        return prereqs

    def construct(self, isPlayer, resourceRefs):
        self.__isObserver = 'observer' in self.typeDescriptor.type.tags
        self._compoundModel = resourceRefs[self.typeDescriptor.name]
        self.removeComponentByType(GenericComponents.DynamicModelComponent)
        self.createComponent(GenericComponents.DynamicModelComponent,
                             self._compoundModel)
        if not self._compoundModel.isValid():
            _logger.error('compoundModel is not valid')
        if self.typeDescriptor.gun.edgeByVisualModel:
            self._compoundModel.setPartProperties(
                TankPartIndexes.GUN, PartProperties.HIGHLIGHTABLE
                | PartProperties.HIGHLIGHTBYVISUAL)
        self._compoundModel.setPartProperties(
            TankPartIndexes.CHASSIS,
            PartProperties.HIGHLIGHTABLE | PartProperties.HIGHLIGHTBYVISUAL)
        self.__boundEffects = bound_effects.ModelBoundEffects(
            self.compoundModel)
        isCurrentModelDamaged = self.damageState.isCurrentModelDamaged
        fashions = camouflages.prepareFashions(isCurrentModelDamaged)
        if not isCurrentModelDamaged:
            model_assembler.setupTracksFashion(self.typeDescriptor,
                                               fashions.chassis)
        self.collisions = self.createComponent(
            BigWorld.CollisionComponent, resourceRefs['collisionAssembler'])
        model_assembler.setupCollisions(self.typeDescriptor, self.collisions)
        self._setFashions(fashions, self.isTurretDetached)
        self._setupModels()
        if not isCurrentModelDamaged:
            modelsSet = self.outfit.modelsSet
            if IS_EDITOR:
                modelsSet = self.currentModelsSet
            self._splineTracks = model_assembler.setupSplineTracks(
                self.fashion, self.typeDescriptor, self.compoundModel,
                resourceRefs, modelsSet)
            self.crashedTracksController = CrashedTrackController(
                self.typeDescriptor, self.fashion, modelsSet)
        else:
            self.__trackScrollCtl = None
        self._chassisDecal.create()
        if self.modelsSetParams.state == 'undamaged':
            self.__modelAnimators = camouflages.getModelAnimators(
                self.outfit, self.typeDescriptor, self.spaceID, resourceRefs,
                self.compoundModel)
            self.__modelAnimators.extend(
                camouflages.getAttachmentsAnimators(self.__attachments,
                                                    self.spaceID, resourceRefs,
                                                    self.compoundModel))
        self.transform = self.createComponent(
            GenericComponents.TransformComponent, Math.Vector3(0, 0, 0))
        self.areaTriggerTarget = self.createComponent(
            Triggers.AreaTriggerTarget)
        self.__filter = model_assembler.createVehicleFilter(
            self.typeDescriptor)
        compoundModel = self.compoundModel
        if self.isAlive:
            self.detailedEngineState, self.gearbox = model_assembler.assembleDrivetrain(
                self, isPlayer)
            if not gEffectsDisabled():
                self.customEffectManager = CustomEffectManager(self)
                if self.typeDescriptor.hasSiegeMode:
                    self.siegeEffects = SiegeEffectsController(self, isPlayer)
                model_assembler.assembleVehicleAudition(isPlayer, self)
                self.detailedEngineState.onEngineStart = self._onEngineStart
                self.detailedEngineState.onStateChanged = self.engineAudition.onEngineStateChanged
            if isPlayer:
                turret = self.typeDescriptor.turret
                gunRotatorAudition = self.createComponent(
                    Vehicular.GunRotatorAudition,
                    turret.turretRotatorSoundManual, turret.weight / 1000.0,
                    compoundModel.node(TankPartNames.TURRET))
                gunRotatorAudition.vehicleMatrixLink = self.compoundModel.root
                gunRotatorAudition.damaged = lambda: self.turretDamaged()
                gunRotatorAudition.maxTurretRotationSpeed = lambda: self.maxTurretRotationSpeed(
                )
                self.gunRotatorAudition = gunRotatorAudition
                self.frictionAudition = self.createComponent(
                    Vehicular.FrictionAudition, TANK_FRICTION_EVENT)
        isLodTopPriority = isPlayer
        lodCalcInst = self.createComponent(
            Vehicular.LodCalculator,
            DataLinks.linkMatrixTranslation(compoundModel.matrix), True,
            VEHICLE_PRIORITY_GROUP, isLodTopPriority)
        self.lodCalculator = lodCalcInst
        self.allLodCalculators.append(lodCalcInst)
        lodLink = DataLinks.createFloatLink(lodCalcInst, 'lodDistance')
        lodStateLink = lodCalcInst.lodStateLink
        if IS_EDITOR:
            matrixBinding = None
            changeCamera = None
        else:
            matrixBinding = BigWorld.player(
            ).consistentMatrices.onVehicleMatrixBindingChanged
            changeCamera = BigWorld.player().inputHandler.onCameraChanged
        self.shadowManager = VehicleShadowManager(compoundModel, matrixBinding,
                                                  changeCamera)
        if not self.damageState.isCurrentModelDamaged:
            self.__assembleNonDamagedOnly(resourceRefs, isPlayer, lodLink,
                                          lodStateLink)
            dirtEnabled = BigWorld.WG_dirtEnabled(
            ) and 'HD' in self.typeDescriptor.type.tags
            if dirtEnabled and self.fashions is not None:
                dirtHandlers = [
                    BigWorld.PyDirtHandler(
                        True,
                        compoundModel.node(TankPartNames.CHASSIS).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.HULL).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.TURRET).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.GUN).position.y)
                ]
                modelHeight, _ = self.computeVehicleHeight()
                self.dirtComponent = self.createComponent(
                    Vehicular.DirtComponent, dirtHandlers, modelHeight)
                for fashionIdx, _ in enumerate(TankPartNames.ALL):
                    self.fashions[fashionIdx].addMaterialHandler(
                        dirtHandlers[fashionIdx])
                    self.fashions[fashionIdx].addTrackMaterialHandler(
                        dirtHandlers[fashionIdx])

        model_assembler.setupTurretRotations(self)
        self.waterSensor = model_assembler.assembleWaterSensor(
            self.typeDescriptor, self, lodStateLink, self.spaceID)
        if self.engineAudition is not None:
            self.engineAudition.setIsUnderwaterInfo(
                DataLinks.createBoolLink(self.waterSensor, 'isUnderWater'))
            self.engineAudition.setIsInWaterInfo(
                DataLinks.createBoolLink(self.waterSensor, 'isInWater'))
        self.__postSetupFilter()
        compoundModel.setPartBoundingBoxAttachNode(
            TankPartIndexes.GUN, TankNodeNames.GUN_INCLINATION)
        camouflages.updateFashions(self)
        model_assembler.assembleCustomLogicComponents(self,
                                                      self.typeDescriptor,
                                                      self.__attachments,
                                                      self.__modelAnimators)
        self._createStickers()
        while self._loadingQueue:
            prefab, go, vector, callback = self._loadingQueue.pop()
            CGF.loadGameObjectIntoHierarchy(prefab, go, vector, callback)

        return

    def destroy(self):
        self._vehicleInfo = {}
        self.flagComponent = None
        self._destroySystems()
        fashions = VehiclePartsTuple(None, None, None, None)
        self._setFashions(fashions, self._isTurretDetached)
        self.shadowManager.unregisterCompoundModel(self.compoundModel)
        for go in self.filterRetrieverGameObjects:
            go.destroy()

        self.wheelsGameObject.destroy()
        super(CommonTankAppearance, self).destroy()
        self.__typeDesc = None
        if self.boundEffects is not None:
            self.boundEffects.destroy()
        self._vehicleStickers = None
        self._chassisDecal.destroy()
        self._chassisDecal = None
        self._compoundModel = None
        self._destroyStickers()
        self._loadingQueue = []
        return

    def activate(self):
        typeDescr = self.typeDescriptor
        wheelConfig = typeDescr.chassis.generalWheelsAnimatorConfig
        if self.wheelsAnimator is not None and wheelConfig is not None:
            self.wheelsAnimator.createCollision(wheelConfig, self.collisions)
        super(CommonTankAppearance, self).activate()
        self.wheelsGameObject.activate()
        for go in self.filterRetrieverGameObjects:
            go.activate()

        if not self.isObserver:
            self._chassisDecal.attach()
        if not self.isObserver:
            self._startSystems()
            self.filter.enableLagDetection(
                not self.damageState.isCurrentModelDamaged)
            if self.__periodicTimerID is not None:
                BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = BigWorld.callback(PERIODIC_UPDATE_TIME,
                                                       self.__onPeriodicTimer)
        self.setupGunMatrixTargets(self.filter)
        for lodCalculator in self.allLodCalculators:
            lodCalculator.setupPosition(
                DataLinks.linkMatrixTranslation(self.compoundModel.matrix))

        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.setEnabled(True)
            modelAnimator.animator.start()

        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.isObserver:
            self.compoundModel.visible = False
        self._connectCollider()
        self._attachStickers()
        return

    def deactivate(self):
        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.setEnabled(False)

        super(CommonTankAppearance, self).deactivate()
        self.shadowManager.unregisterCompoundModel(self.compoundModel)
        self._stopSystems()
        self.wheelsGameObject.deactivate()
        for go in self.filterRetrieverGameObjects:
            go.deactivate()

        self._chassisDecal.detach()
        self._detachStickers()

    def setVehicleInfo(self, vehInfo):
        self._vehicleInfo = vehInfo

    def setupGunMatrixTargets(self, target):
        self.turretMatrix = target.turretMatrix
        self.gunMatrix = target.gunMatrix

    def receiveShotImpulse(self, direction, impulse):
        if not VehicleDamageState.isDamagedModel(self.damageState.modelState):
            self.swingingAnimator.receiveShotImpulse(direction, impulse)
            if self.crashedTracksController is not None:
                self.crashedTracksController.receiveShotImpulse(
                    direction, impulse)
        return

    def recoil(self):
        self._initiateRecoil(TankNodeNames.GUN_INCLINATION, 'HP_gunFire',
                             self.gunRecoil)

    def multiGunRecoil(self, indexes):
        if self.gunAnimators is None:
            return
        else:
            for index in indexes:
                typeDescr = self.typeDescriptor
                gunNodeName = typeDescr.turret.multiGun[index].node
                gunFireNodeName = typeDescr.turret.multiGun[index].gunFire
                gunAnimator = self.gunAnimators[index].findComponentByType(
                    Vehicular.RecoilAnimator)
                self._initiateRecoil(gunNodeName, gunFireNodeName, gunAnimator)

            return

    def computeFullVehicleLength(self):
        vehicleLength = 0.0
        if self.compoundModel is not None:
            hullBB = Math.Matrix(
                self.compoundModel.getBoundsForPart(TankPartIndexes.HULL))
            vehicleLength = hullBB.applyVector(Math.Vector3(0.0, 0.0,
                                                            1.0)).length
        return vehicleLength

    def _initiateRecoil(self, gunNodeName, gunFireNodeName, gunAnimator):
        gunNode = self.compoundModel.node(gunNodeName)
        impulseDir = Math.Matrix(gunNode).applyVector(Math.Vector3(0, 0, -1))
        impulseValue = self.typeDescriptor.gun.impulse
        self.receiveShotImpulse(impulseDir, impulseValue)
        gunAnimator.recoil()
        return impulseDir

    def _connectCollider(self):
        if self.collisions is not None:
            chassisColisionMatrix, gunNodeName = self._vehicleColliderInfo
            if self.isTurretDetached:
                self.collisions.removeAttachment(
                    TankPartNames.getIdx(TankPartNames.TURRET))
                self.collisions.removeAttachment(
                    TankPartNames.getIdx(TankPartNames.GUN))
                collisionData = ((TankPartNames.getIdx(TankPartNames.HULL),
                                  self.compoundModel.node(TankPartNames.HULL)),
                                 (TankPartNames.getIdx(TankPartNames.CHASSIS),
                                  chassisColisionMatrix))
            else:
                collisionData = ((TankPartNames.getIdx(TankPartNames.HULL),
                                  self.compoundModel.node(TankPartNames.HULL)),
                                 (TankPartNames.getIdx(TankPartNames.TURRET),
                                  self.compoundModel.node(
                                      TankPartNames.TURRET)),
                                 (TankPartNames.getIdx(TankPartNames.CHASSIS),
                                  chassisColisionMatrix),
                                 (TankPartNames.getIdx(TankPartNames.GUN),
                                  self.compoundModel.node(gunNodeName)))
            defaultPartLength = len(TankPartNames.ALL)
            additionalChassisParts = []
            trackPairs = self.typeDescriptor.chassis.trackPairs
            if not trackPairs:
                trackPairs = [None]
            for x in xrange(len(trackPairs) - 1):
                additionalChassisParts.append(
                    (defaultPartLength + x, chassisColisionMatrix))

            if additionalChassisParts:
                collisionData += tuple(additionalChassisParts)
            self.collisions.connect(self.id, ColliderTypes.VEHICLE_COLLIDER,
                                    collisionData)
        return

    def computeVehicleHeight(self):
        gunLength = 0.0
        height = 0.0
        if self.collisions is not None:
            desc = self.typeDescriptor
            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 onWaterSplash(self, waterHitPoint, isHeavySplash):
        pass

    def onUnderWaterSwitch(self, isUnderWater):
        pass

    def getWheelsSteeringMax(self):
        pass

    def _prepareOutfit(self, outfitCD):
        outfitComponent = camouflages.getOutfitComponent(outfitCD)
        return Outfit(component=outfitComponent,
                      vehicleCD=self.typeDescriptor.makeCompactDescr())

    def _setupModels(self):
        self.__isAlive = not self.damageState.isCurrentModelDamaged
        if self.isAlive:
            _, gunLength = self.computeVehicleHeight()
            self.__weaponEnergy = gunLength * self.typeDescriptor.shot.shell.caliber
        if MAX_DISTANCE > 0 and not self.isObserver:
            transform = self.typeDescriptor.chassis.AODecals[0]
            splodge = BigWorld.Splodge(
                transform, MAX_DISTANCE,
                self.typeDescriptor.chassis.hullPosition.y)
            if splodge:
                self.__splodge = splodge
                node = self.compoundModel.node(TankPartNames.HULL)
                node.attach(splodge)

    def _createStickers(self):
        _logger.debug('Creating VehicleStickers for vehicleType: %s',
                      self.typeDescriptor)
        isCurrentModelDamaged = self.damageState.isCurrentModelDamaged
        if isCurrentModelDamaged:
            return
        else:
            if self.vehicleStickers is not None:
                self._destroyStickers()
            self._vehicleStickers = VehicleStickers(self.spaceID,
                                                    self.typeDescriptor,
                                                    outfit=self.outfit)
            return

    def _destroyStickers(self):
        _logger.debug('Attaching VehicleStickers for vehicleType: %s',
                      self.typeDescriptor)
        self._detachStickers()
        self._vehicleStickers = None
        return

    def _attachStickers(self):
        _logger.debug('Attaching VehicleStickers for vehicle: %s',
                      self._vehicle)
        if self.vehicleStickers is None:
            _logger.error(
                'Failed to attach VehicleStickers. Missing VehicleStickers. Vehicle: %s',
                self._vehicle)
            return
        else:
            isCurrentModelDamaged = self.damageState.isCurrentModelDamaged
            self.vehicleStickers.alpha = DEFAULT_STICKERS_ALPHA
            self.vehicleStickers.attach(
                compoundModel=self.compoundModel,
                isDamaged=isCurrentModelDamaged,
                showDamageStickers=not isCurrentModelDamaged)
            return

    def _detachStickers(self):
        _logger.debug('Detaching VehicleStickers for vehicle: %s',
                      self._vehicle)
        if self.vehicleStickers is not None:
            self.vehicleStickers.detach()
        return

    @property
    def _vehicleColliderInfo(self):
        chassisColisionMatrix = self.compoundModel.matrix
        if self.damageState.isCurrentModelDamaged:
            gunNodeName = 'gun'
        else:
            gunNodeName = TankNodeNames.GUN_INCLINATION
        return (chassisColisionMatrix, gunNodeName)

    def _startSystems(self):
        if self.flyingInfoProvider is not None:
            self.flyingInfoProvider.setData(self.filter, self.suspension)
        if self.damageState.isCurrentModelDamaged or self.__systemStarted:
            return
        else:
            self.__systemStarted = True
            if self.trackScrollController is not None:
                self.trackScrollController.activate()
                self.trackScrollController.setData(self.filter)
            if self.engineAudition is not None:
                self.engineAudition.setWeaponEnergy(self._weaponEnergy)
                self.engineAudition.attachToModel(self.compoundModel)
            if self.hullAimingController is not None:
                self.hullAimingController.setData(self.filter,
                                                  self.typeDescriptor)
            if self.detailedEngineState is not None:
                self.detailedEngineState.onGearUpCbk = self.__onEngineStateGearUp
            return

    def _stopSystems(self):
        if self.flyingInfoProvider is not None:
            self.flyingInfoProvider.setData(None, None)
        if self.__systemStarted:
            self.__systemStarted = False
        if self.trackScrollController is not None:
            self.trackScrollController.deactivate()
            self.trackScrollController.setData(None)
        if self.__periodicTimerID is not None:
            BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = None
        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.stop()

        self.filter.enableLagDetection(False)
        return

    def _destroySystems(self):
        self.__systemStarted = False
        if self.trackScrollController is not None:
            self.trackScrollController.deactivate()
            self.__trackScrollCtl = None
        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.stop()

        if self.__periodicTimerID is not None:
            BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = None
        self.__modelAnimators = []
        self.filter.enableLagDetection(False)
        for go in self.undamagedStateChildren:
            CGF.removeGameObject(go)

        self.undamagedStateChildren = []
        return

    def _onRequestModelsRefresh(self):
        self.flagComponent = None
        self.__updateModelStatus()
        return

    def __updateModelStatus(self):
        if self.damageState.isCurrentModelUndamaged:
            modelStatus = ModelStatus.NORMAL
        else:
            modelStatus = ModelStatus.CRASHED
        for htManager in self.typeDescriptor.getHitTesterManagers():
            htManager.setStatus(modelStatus)

    def _onEngineStart(self):
        if self.engineAudition is not None:
            self.engineAudition.onEngineStart()
        return

    def __assembleNonDamagedOnly(self, resourceRefs, isPlayer, lodLink,
                                 lodStateLink):
        model_assembler.assembleTerrainMatKindSensor(self, lodStateLink,
                                                     self.spaceID)
        model_assembler.assembleRecoil(self, lodLink)
        model_assembler.assembleMultiGunRecoil(self, lodLink)
        model_assembler.assembleGunLinkedNodesAnimator(self)
        model_assembler.assembleCollisionObstaclesCollector(
            self, lodStateLink, self.typeDescriptor)
        model_assembler.assembleTessellationCollisionSensor(self, lodStateLink)
        wheelsScroll = None
        wheelsSteering = None
        generalWheelsAnimatorConfig = self.typeDescriptor.chassis.generalWheelsAnimatorConfig
        if generalWheelsAnimatorConfig is not None:
            scrollableWheelsCount = generalWheelsAnimatorConfig.getNonTrackWheelsCount(
            )
            wheelsScroll = []
            for _ in xrange(scrollableWheelsCount):
                retrieverGameObject = ScriptGameObject(self.spaceID)
                retriever = retrieverGameObject.createComponent(
                    NetworkFilters.FloatFilterRetriever)
                wheelsScroll.append(
                    DataLinks.createFloatLink(retriever, 'value'))
                self.filterRetrievers.append(retriever)
                self.filterRetrieverGameObjects.append(retrieverGameObject)

            steerableWheelsCount = generalWheelsAnimatorConfig.getSteerableWheelsCount(
            )
            wheelsSteering = []
            for _ in xrange(steerableWheelsCount):
                retrieverGameObject = ScriptGameObject(self.spaceID)
                retriever = retrieverGameObject.createComponent(
                    NetworkFilters.FloatFilterRetriever)
                wheelsSteering.append(
                    DataLinks.createFloatLink(retriever, 'value'))
                self.filterRetrievers.append(retriever)
                self.filterRetrieverGameObjects.append(retrieverGameObject)

        self.wheelsAnimator = model_assembler.createWheelsAnimator(
            self, ColliderTypes.VEHICLE_COLLIDER, self.typeDescriptor,
            lambda: self.wheelsState, wheelsScroll, wheelsSteering,
            self.splineTracks, lodStateLink)
        if self.customEffectManager is not None:
            self.customEffectManager.setWheelsData(self)
        suspensionLodLink = lodStateLink
        if 'wheeledVehicle' in self.typeDescriptor.type.tags:
            wheeledLodCalculator = self.wheelsGameObject.createComponent(
                Vehicular.LodCalculator,
                DataLinks.linkMatrixTranslation(self.compoundModel.matrix),
                True, WHEELED_CHASSIS_PRIORITY_GROUP, isPlayer)
            self.allLodCalculators.append(wheeledLodCalculator)
            suspensionLodLink = wheeledLodCalculator.lodStateLink
        model_assembler.assembleSuspensionIfNeed(self, suspensionLodLink)
        model_assembler.assembleLeveredSuspensionIfNeed(
            self, suspensionLodLink)
        self.__assembleSwinging(lodLink)
        model_assembler.assembleBurnoutProcessor(self)
        model_assembler.assembleSuspensionSound(self, lodLink, isPlayer)
        model_assembler.assembleHullAimingController(self)
        self.trackNodesAnimator = model_assembler.createTrackNodesAnimator(
            self, self.typeDescriptor, lodStateLink)
        model_assembler.assembleTracks(resourceRefs, self.typeDescriptor, self,
                                       self.splineTracks, False, lodStateLink)
        model_assembler.assembleVehicleTraces(self, self.filter, lodStateLink)
        return

    def __assembleSwinging(self, lodLink):
        hullNode = self.compoundModel.node(TankPartNames.HULL)
        if hullNode is None:
            _logger.error(
                'Could not create SwingingAnimator: failed to find hull node')
            return
        else:
            self.swingingAnimator = model_assembler.createSwingingAnimator(
                self, self.typeDescriptor, hullNode.localMatrix,
                self.compoundModel.matrix, lodLink)
            self.compoundModel.node(TankPartNames.HULL,
                                    self.swingingAnimator.animatedMProv)
            if hasattr(self.filter, 'placingCompensationMatrix'):
                self.swingingAnimator.placingCompensationMatrix = self.filter.placingCompensationMatrix
            return

    def __postSetupFilter(self):
        suspensionWorking = self.suspension is not None and self.suspension.hasGroundNodes
        placingOnGround = not (suspensionWorking
                               or self.leveredSuspension is not None)
        self.filter.placingOnGround = placingOnGround
        return

    def __onPeriodicTimer(self):
        timeStamp = BigWorld.wg_getFrameTimestamp()
        if self.__frameTimestamp >= timeStamp:
            self.__periodicTimerID = BigWorld.callback(0.0,
                                                       self.__onPeriodicTimer)
        else:
            self.__frameTimestamp = timeStamp
            self.__periodicTimerID = BigWorld.callback(PERIODIC_UPDATE_TIME,
                                                       self.__onPeriodicTimer)
            self._periodicUpdate()

    def _periodicUpdate(self):
        if self._vehicle is None or not self._vehicle.isAlive():
            return
        else:
            self._updateCurrTerrainMatKinds()
            self.__updateEffectsLOD()
            if self.siegeEffects:
                self.siegeEffects.tick()
            if self.customEffectManager:
                self.customEffectManager.update()
            return

    def __updateEffectsLOD(self):
        if self.customEffectManager:
            distanceFromPlayer = self.lodCalculator.lodDistance
            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 _stopEffects(self):
        self.boundEffects.stop()

    def playEffectWithStopCallback(self, effects):
        self._stopEffects()
        vehicle = self._vehicle
        return self.boundEffects.addNew(
            None,
            effects[1],
            effects[0],
            isPlayerVehicle=vehicle.isPlayerVehicle,
            showShockWave=vehicle.isPlayerVehicle,
            showFlashBang=vehicle.isPlayerVehicle,
            entity_id=vehicle.id,
            isPlayer=vehicle.isPlayerVehicle,
            showDecal=True,
            start=vehicle.position + Math.Vector3(0.0, 1.0, 0.0),
            end=vehicle.position + Math.Vector3(0.0, -1.0, 0.0)).stop

    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)
            args = dict(isPlayerVehicle=vehicle.isPlayerVehicle,
                        showShockWave=vehicle.isPlayerVehicle,
                        showFlashBang=vehicle.isPlayerVehicle,
                        entity_id=vehicle.id,
                        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))
            if isSpawnedBot(
                    self.typeDescriptor.type.tags) and kind in ('explosion',
                                                                'destruction'):
                if isPlayerAvatar():
                    if self.isFlying:
                        instantExplosionEff = self.typeDescriptor.type.effects[
                            'instantExplosion']
                        if instantExplosionEff:
                            effects = random.choice(instantExplosionEff)
                    BigWorld.player().terrainEffects.addNew(
                        self._vehicle.position, effects[1], effects[0], None,
                        **args)
            else:
                self.boundEffects.addNew(None, effects[1], effects[0], **args)
            return

    def _updateCurrTerrainMatKinds(self):
        if self.terrainMatKindSensor is None:
            return
        else:
            matKinds = self.terrainMatKindSensor.matKinds
            groundTypes = self.terrainMatKindSensor.groundTypes
            materialsCount = len(matKinds)
            for i in xrange(MATKIND_COUNT):
                matKind = matKinds[i] if i < materialsCount else 0
                groundType = groundTypes[i] if i < materialsCount else 0
                self.terrainMatKind[i] = matKind
                self.terrainGroundType[i] = groundType
                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.terrainMatKind[0], self.terrainMatKind[1])
            return

    def onSiegeStateChanged(self, newState, timeToNextMode):
        if self.engineAudition is not None:
            self.engineAudition.onSiegeStateChanged(newState)
        if self.hullAimingController is not None:
            self.hullAimingController.onSiegeStateChanged(newState)
        if self.suspensionSound is not None:
            self.suspensionSound.vehicleState = newState
        if self.siegeEffects is not None:
            self.siegeEffects.onSiegeStateChanged(newState, timeToNextMode)
        enabled = newState == VEHICLE_SIEGE_STATE.ENABLED or newState == VEHICLE_SIEGE_STATE.SWITCHING_ON
        if self.suspension is not None:
            self.suspension.setLiftMode(enabled)
        if self.leveredSuspension is not None:
            self.leveredSuspension.setLiftMode(enabled)
        if self.vehicleTraces is not None:
            self.vehicleTraces.setLiftMode(enabled)
        return

    def changeEngineMode(self, mode, forceSwinging=False):
        if self.detailedEngineState is not None:
            self.detailedEngineState.mode = mode[0]
        if self.trackScrollController is not None:
            self.trackScrollController.setMode(mode)
        return

    def changeSiegeState(self, siegeState):
        if self.engineAudition is not None:
            self.engineAudition.onSiegeStateChanged(siegeState)
        return

    def turretDamaged(self):
        pass

    def maxTurretRotationSpeed(self):
        pass

    def pushToLoadingQueue(self, prefab, go, vector, callback):
        self._loadingQueue.append((prefab, go, vector, callback))

    def _onCameraChanged(self, cameraName, currentVehicleId=None):
        if self.id != BigWorld.player().playerVehicleID:
            return
        isEnabled = not cameraName == 'sniper'
        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.setEnabled(isEnabled)

    def __onEngineStateGearUp(self):
        if self.customEffectManager is not None:
            self.customEffectManager.onGearUp()
        if self.engineAudition is not None:
            self.engineAudition.onEngineGearUp()
        return

    def __animatorCallback(self, name, time):
        _logger.debug('Callback aquired %s %f', name, time)
        if self.shellAnimator is not None:
            self.shellAnimator.throwShell(
                self.typeDescriptor.shot.shell.animation)
        return

    def __isRequireTrackDebrisGeneration(self, isLeft, pairIndex):
        tracks = self.typeDescriptor.chassis.tracks
        return tracks is not None and tracks.trackPairs[
            pairIndex].tracksDebris is not None

    def _addCrashedTrack(self, isLeft, pairIndex, isSideFlying):
        if not self.__isRequireTrackDebrisGeneration(isLeft, pairIndex):
            if self.crashedTracksController is not None:
                self.crashedTracksController.addTrack(isLeft, isSideFlying)
            return
        else:
            track = self.tracks.getTrackGameObject(isLeft, pairIndex)
            debris = track.createComponent(TrackCrashWithDebrisComponent,
                                           isLeft, pairIndex,
                                           self.typeDescriptor,
                                           self.gameObject, self.boundEffects)
            debris.isTopPriority = self._vehicle.isPlayerVehicle
            debris.isPlayer = self._vehicle.isPlayerVehicle
            debris.isFlying = isSideFlying
            return

    def _delCrashedTrack(self, isLeft, pairIndex):
        if not self.__isRequireTrackDebrisGeneration(isLeft, pairIndex):
            if self.crashedTracksController is not None:
                self.crashedTracksController.delTrack(isLeft)
            return
        elif self.tracks is None:
            return
        else:
            track = self.tracks.getTrackGameObject(isLeft, pairIndex)
            debris = track.findComponentByType(TrackCrashWithDebrisComponent)
            if debris is not None:
                debris.markAsRepaired()
                track.removeComponent(debris)
            return
    def construct(self, isPlayer, resourceRefs):
        self.__isObserver = 'observer' in self.typeDescriptor.type.tags
        self._compoundModel = resourceRefs[self.typeDescriptor.name]
        self.removeComponentByType(GenericComponents.DynamicModelComponent)
        self.createComponent(GenericComponents.DynamicModelComponent,
                             self._compoundModel)
        if not self._compoundModel.isValid():
            _logger.error('compoundModel is not valid')
        if self.typeDescriptor.gun.edgeByVisualModel:
            self._compoundModel.setPartProperties(
                TankPartIndexes.GUN, PartProperties.HIGHLIGHTABLE
                | PartProperties.HIGHLIGHTBYVISUAL)
        self._compoundModel.setPartProperties(
            TankPartIndexes.CHASSIS,
            PartProperties.HIGHLIGHTABLE | PartProperties.HIGHLIGHTBYVISUAL)
        self.__boundEffects = bound_effects.ModelBoundEffects(
            self.compoundModel)
        isCurrentModelDamaged = self.damageState.isCurrentModelDamaged
        fashions = camouflages.prepareFashions(isCurrentModelDamaged)
        if not isCurrentModelDamaged:
            model_assembler.setupTracksFashion(self.typeDescriptor,
                                               fashions.chassis)
        self.collisions = self.createComponent(
            BigWorld.CollisionComponent, resourceRefs['collisionAssembler'])
        model_assembler.setupCollisions(self.typeDescriptor, self.collisions)
        self._setFashions(fashions, self.isTurretDetached)
        self._setupModels()
        if not isCurrentModelDamaged:
            modelsSet = self.outfit.modelsSet
            if IS_EDITOR:
                modelsSet = self.currentModelsSet
            self._splineTracks = model_assembler.setupSplineTracks(
                self.fashion, self.typeDescriptor, self.compoundModel,
                resourceRefs, modelsSet)
            self.crashedTracksController = CrashedTrackController(
                self.typeDescriptor, self.fashion, modelsSet)
        else:
            self.__trackScrollCtl = None
        self._chassisDecal.create()
        if self.modelsSetParams.state == 'undamaged':
            self.__modelAnimators = camouflages.getModelAnimators(
                self.outfit, self.typeDescriptor, self.spaceID, resourceRefs,
                self.compoundModel)
            self.__modelAnimators.extend(
                camouflages.getAttachmentsAnimators(self.__attachments,
                                                    self.spaceID, resourceRefs,
                                                    self.compoundModel))
        self.transform = self.createComponent(
            GenericComponents.TransformComponent, Math.Vector3(0, 0, 0))
        self.areaTriggerTarget = self.createComponent(
            Triggers.AreaTriggerTarget)
        self.__filter = model_assembler.createVehicleFilter(
            self.typeDescriptor)
        compoundModel = self.compoundModel
        if self.isAlive:
            self.detailedEngineState, self.gearbox = model_assembler.assembleDrivetrain(
                self, isPlayer)
            if not gEffectsDisabled():
                self.customEffectManager = CustomEffectManager(self)
                if self.typeDescriptor.hasSiegeMode:
                    self.siegeEffects = SiegeEffectsController(self, isPlayer)
                model_assembler.assembleVehicleAudition(isPlayer, self)
                self.detailedEngineState.onEngineStart = self._onEngineStart
                self.detailedEngineState.onStateChanged = self.engineAudition.onEngineStateChanged
            if isPlayer:
                turret = self.typeDescriptor.turret
                gunRotatorAudition = self.createComponent(
                    Vehicular.GunRotatorAudition,
                    turret.turretRotatorSoundManual, turret.weight / 1000.0,
                    compoundModel.node(TankPartNames.TURRET))
                gunRotatorAudition.vehicleMatrixLink = self.compoundModel.root
                gunRotatorAudition.damaged = lambda: self.turretDamaged()
                gunRotatorAudition.maxTurretRotationSpeed = lambda: self.maxTurretRotationSpeed(
                )
                self.gunRotatorAudition = gunRotatorAudition
                self.frictionAudition = self.createComponent(
                    Vehicular.FrictionAudition, TANK_FRICTION_EVENT)
        isLodTopPriority = isPlayer
        lodCalcInst = self.createComponent(
            Vehicular.LodCalculator,
            DataLinks.linkMatrixTranslation(compoundModel.matrix), True,
            VEHICLE_PRIORITY_GROUP, isLodTopPriority)
        self.lodCalculator = lodCalcInst
        self.allLodCalculators.append(lodCalcInst)
        lodLink = DataLinks.createFloatLink(lodCalcInst, 'lodDistance')
        lodStateLink = lodCalcInst.lodStateLink
        if IS_EDITOR:
            matrixBinding = None
            changeCamera = None
        else:
            matrixBinding = BigWorld.player(
            ).consistentMatrices.onVehicleMatrixBindingChanged
            changeCamera = BigWorld.player().inputHandler.onCameraChanged
        self.shadowManager = VehicleShadowManager(compoundModel, matrixBinding,
                                                  changeCamera)
        if not self.damageState.isCurrentModelDamaged:
            self.__assembleNonDamagedOnly(resourceRefs, isPlayer, lodLink,
                                          lodStateLink)
            dirtEnabled = BigWorld.WG_dirtEnabled(
            ) and 'HD' in self.typeDescriptor.type.tags
            if dirtEnabled and self.fashions is not None:
                dirtHandlers = [
                    BigWorld.PyDirtHandler(
                        True,
                        compoundModel.node(TankPartNames.CHASSIS).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.HULL).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.TURRET).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.GUN).position.y)
                ]
                modelHeight, _ = self.computeVehicleHeight()
                self.dirtComponent = self.createComponent(
                    Vehicular.DirtComponent, dirtHandlers, modelHeight)
                for fashionIdx, _ in enumerate(TankPartNames.ALL):
                    self.fashions[fashionIdx].addMaterialHandler(
                        dirtHandlers[fashionIdx])
                    self.fashions[fashionIdx].addTrackMaterialHandler(
                        dirtHandlers[fashionIdx])

        model_assembler.setupTurretRotations(self)
        self.waterSensor = model_assembler.assembleWaterSensor(
            self.typeDescriptor, self, lodStateLink, self.spaceID)
        if self.engineAudition is not None:
            self.engineAudition.setIsUnderwaterInfo(
                DataLinks.createBoolLink(self.waterSensor, 'isUnderWater'))
            self.engineAudition.setIsInWaterInfo(
                DataLinks.createBoolLink(self.waterSensor, 'isInWater'))
        self.__postSetupFilter()
        compoundModel.setPartBoundingBoxAttachNode(
            TankPartIndexes.GUN, TankNodeNames.GUN_INCLINATION)
        camouflages.updateFashions(self)
        model_assembler.assembleCustomLogicComponents(self,
                                                      self.typeDescriptor,
                                                      self.__attachments,
                                                      self.__modelAnimators)
        self._createStickers()
        while self._loadingQueue:
            prefab, go, vector, callback = self._loadingQueue.pop()
            CGF.loadGameObjectIntoHierarchy(prefab, go, vector, callback)

        return
    def _assembleParts(self, isPlayer, appearance, resourceRefs):
        appearance.filter = model_assembler.createVehicleFilter(
            appearance.typeDescriptor)
        if appearance.isAlive:
            appearance.detailedEngineState = model_assembler.assembleDetailedEngineState(
                appearance.compoundModel, appearance.filter,
                appearance.typeDescriptor, isPlayer)
            if not gEffectsDisabled():
                model_assembler.assembleVehicleAudition(isPlayer, appearance)
                model_assembler.subscribeEngineAuditionToEngineState(
                    appearance.engineAudition, appearance.detailedEngineState)
                createEffects(appearance)
            if isPlayer:
                gunRotatorConnector = GunRotatorConnector(appearance)
                appearance.addComponent(gunRotatorConnector)
                appearance.frictionAudition = Vehicular.FrictionAudition(
                    TANK_FRICTION_EVENT)
                appearance.peripheralsController = PeripheralsController()
        self.__createTrackCrashControl(appearance)
        appearance.highlighter = Highlighter()
        compoundModel = appearance.compoundModel
        isLodTopPriority = isPlayer
        lodCalcInst = Vehicular.LodCalculator(
            DataLinks.linkMatrixTranslation(appearance.compoundModel.matrix),
            True, VEHICLE_PRIORITY_GROUP, isLodTopPriority)
        appearance.lodCalculator = lodCalcInst
        lodLink = DataLinks.createFloatLink(lodCalcInst, 'lodDistance')
        lodStateLink = lodCalcInst.lodStateLink
        matrixBinding = BigWorld.player(
        ).consistentMatrices.onVehicleMatrixBindingChanged
        appearance.shadowManager = VehicleShadowManager(
            compoundModel, matrixBinding)
        isDamaged = appearance.damageState.isCurrentModelDamaged
        if not isDamaged:
            self.__assembleNonDamagedOnly(appearance, isPlayer, lodLink,
                                          lodStateLink)
            dirtEnabled = BigWorld.WG_dirtEnabled(
            ) and 'HD' in appearance.typeDescriptor.type.tags
            fashions = appearance.fashions
            if dirtEnabled and fashions is not None:
                dirtHandlers = [
                    BigWorld.PyDirtHandler(
                        True,
                        compoundModel.node(TankPartNames.CHASSIS).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.HULL).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.TURRET).position.y),
                    BigWorld.PyDirtHandler(
                        False,
                        compoundModel.node(TankPartNames.GUN).position.y)
                ]
                modelHeight, _ = appearance.computeVehicleHeight()
                appearance.dirtComponent = Vehicular.DirtComponent(
                    dirtHandlers, modelHeight)
                for fashionIdx, _ in enumerate(TankPartNames.ALL):
                    fashions[fashionIdx].addMaterialHandler(
                        dirtHandlers[fashionIdx])

        model_assembler.setupTurretRotations(appearance)
        if appearance.fashion is not None:
            appearance.fashion.movementInfo = appearance.filter.movementInfo
        appearance.waterSensor = model_assembler.assembleWaterSensor(
            appearance.typeDescriptor, appearance, lodStateLink)
        if appearance.engineAudition is not None:
            appearance.engineAudition.setIsUnderwaterInfo(
                DataLinks.createBoolLink(appearance.waterSensor,
                                         'isUnderWater'))
            appearance.engineAudition.setIsInWaterInfo(
                DataLinks.createBoolLink(appearance.waterSensor, 'isInWater'))
        if isPlayer and BigWorld.player().isInTutorial:
            tutorialMatKindsController = TutorialMatKindsController()
            tutorialMatKindsController.terrainMatKindsLink = lambda: appearance.terrainMatKind
            appearance.addComponent(tutorialMatKindsController)
        self.__postSetupFilter(appearance)
        compoundModel.setPartBoundingBoxAttachNode(
            TankPartIndexes.GUN, TankNodeNames.GUN_INCLINATION)
        return
class CommonTankAppearance(ScriptGameObject):
    compoundModel = property(lambda self: self._compoundModel)
    boundEffects = property(lambda self: self.__boundEffects)
    fashions = property(lambda self: self.__fashions)
    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)
    outfit = property(lambda self: self.__outfit)
    renderState = property(lambda self: self.__renderState)

    def _setFashions(self, fashions, isTurretDetached=False):
        self.__fashions = fashions
        if isTurretDetached:
            self.compoundModel.setupFashions((fashions.chassis, fashions.hull))
        else:
            self.compoundModel.setupFashions(fashions)

    terrainMatKind = property(lambda self: self.__currTerrainMatKind)
    terrainGroundType = property(lambda self: self.__currTerrainGroundType)
    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)
    modelsSetParams = property(lambda self: ModelsSetParams(self.outfit.modelsSet, self.damageState.modelState, self.__attachments))
    splineTracks = property(lambda self: self._splineTracks)
    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)
    trackScrollController = property(lambda self: self.__trackScrollCtl)
    wheelsState = property(lambda self: 0)
    burnoutLevel = property(lambda self: 0.0)
    filterRetrievers = property(lambda self: self.__filterRetrievers)
    allLodCalculators = property(lambda self: self.__allLodCalculators)
    transmissionSlip = property(lambda self: self._commonSlip)
    transmissionScroll = property(lambda self: self._commonScroll)
    vehicleStickers = property(lambda self: self._vehicleStickers)
    isTurretDetached = property(lambda self: self._isTurretDetached)
    _weaponEnergy = property(lambda self: self.__weaponEnergy)
    filter = AutoProperty()
    areaTriggerTarget = ComponentDescriptor()
    burnoutProcessor = ComponentDescriptor()
    c11nComponent = ComponentDescriptor()
    collisionObstaclesCollector = ComponentDescriptor()
    collisions = ComponentDescriptor()
    crashedTracksController = ComponentDescriptor()
    customEffectManager = ComponentDescriptor()
    detailedEngineState = ComponentDescriptor()
    dirtComponent = ComponentDescriptor()
    engineAudition = ComponentDescriptor()
    flyingInfoProvider = ComponentDescriptor()
    frictionAudition = ComponentDescriptor()
    gearbox = ComponentDescriptor()
    gunLinkedNodesAnimator = ComponentDescriptor()
    gunRecoil = ComponentDescriptor()
    gunAnimators = [ComponentDescriptor()]
    gunRotatorAudition = ComponentDescriptor()
    hullAimingController = ComponentDescriptor()
    leveredSuspension = ComponentDescriptor()
    lodCalculator = ComponentDescriptor()
    shadowManager = ComponentDescriptor()
    siegeEffects = ComponentDescriptor()
    suspension = ComponentDescriptor()
    suspensionSound = ComponentDescriptor()
    swingingAnimator = ComponentDescriptor()
    terrainMatKindSensor = ComponentDescriptor()
    tessellationCollisionSensor = ComponentDescriptor()
    trackNodesAnimator = ComponentDescriptor()
    tracks = ComponentDescriptor()
    transform = ComponentDescriptor()
    vehicleTraces = ComponentDescriptor()
    waterSensor = ComponentDescriptor()
    wheeledLodCalculator = ComponentDescriptor()
    wheelsAnimator = ComponentDescriptor()
    flagComponent = ComponentDescriptor()

    def __init__(self, spaceID):
        ScriptGameObject.__init__(self, spaceID)
        self._vehicle = None
        self.__filter = None
        self.__typeDesc = None
        self.crashedTracksController = None
        self.__currentDamageState = VehicleDamageState()
        self.__currTerrainMatKind = [-1] * MATKIND_COUNT
        self.__currTerrainGroundType = [-1] * MATKIND_COUNT
        self.__terrainEffectMaterialNames = [''] * MATKIND_COUNT
        self._chassisDecal = VehicleDecal(self)
        self.__splodge = None
        self.__boundEffects = None
        self._splineTracks = None
        self.flyingInfoProvider = self.createComponent(Vehicular.FlyingInfoProvider)
        self.__trackScrollCtl = BigWorld.PyTrackScroll()
        self.__trackScrollCtl.setFlyingInfo(DataLinks.createBoolLink(self.flyingInfoProvider, 'isLeftSideFlying'), DataLinks.createBoolLink(self.flyingInfoProvider, 'isRightSideFlying'))
        self.__weaponEnergy = 0.0
        self.__outfit = None
        self.__systemStarted = False
        self.__isAlive = True
        self._isTurretDetached = False
        self.__isObserver = False
        self.__attachments = []
        self.__modelAnimators = []
        self.turretMatrix = None
        self.gunMatrix = None
        self.__allLodCalculators = []
        self._commonScroll = 0.0
        self._commonSlip = 0.0
        self._compoundModel = None
        self.__fashions = None
        self.__filterRetrievers = []
        self._vehicleStickers = None
        self.__vID = 0
        self.__renderState = None
        self.__frameTimestamp = 0
        self.__periodicTimerID = None
        return

    def prerequisites(self, typeDescriptor, vID, health, isCrewActive, isTurretDetached, outfitCD, renderState=None):
        self.damageState.update(health, isCrewActive, False)
        self.__typeDesc = typeDescriptor
        self.__vID = vID
        self._isTurretDetached = isTurretDetached
        self.__outfit = self._prepareOutfit(outfitCD)
        if self.damageState.isCurrentModelUndamaged:
            self.__attachments = camouflages.getAttachments(self.outfit, self.typeDescriptor)
        self.__renderState = renderState
        prereqs = self.typeDescriptor.prerequisites(True)
        prereqs.extend(camouflages.getCamoPrereqs(self.outfit, self.typeDescriptor))
        prereqs.extend(camouflages.getModelAnimatorsPrereqs(self.outfit, self.worldID))
        prereqs.extend(camouflages.getAttachmentsAnimatorsPrereqs(self.__attachments, self.worldID))
        splineDesc = self.typeDescriptor.chassis.splineDesc
        if splineDesc is not None:
            modelsSet = self.outfit.modelsSet
            prereqs.append(splineDesc.segmentModelLeft(modelsSet))
            prereqs.append(splineDesc.segmentModelRight(modelsSet))
            segment2ModelLeft = splineDesc.segment2ModelLeft(modelsSet)
            if segment2ModelLeft is not None:
                prereqs.append(segment2ModelLeft)
            segment2ModelRight = splineDesc.segment2ModelRight(modelsSet)
            if segment2ModelRight is not None:
                prereqs.append(segment2ModelRight)
        modelsSetParams = self.modelsSetParams
        compoundAssembler = model_assembler.prepareCompoundAssembler(self.typeDescriptor, modelsSetParams, self.worldID, self.isTurretDetached, renderState=self.renderState)
        prereqs.append(compoundAssembler)
        if renderState == RenderStates.OVERLAY_COLLISION:
            self.damageState.update(0, isCrewActive, False)
        if not isTurretDetached:
            bspModels = ((TankPartNames.getIdx(TankPartNames.CHASSIS), typeDescriptor.chassis.hitTester.bspModelName),
             (TankPartNames.getIdx(TankPartNames.HULL), typeDescriptor.hull.hitTester.bspModelName),
             (TankPartNames.getIdx(TankPartNames.TURRET), typeDescriptor.turret.hitTester.bspModelName),
             (TankPartNames.getIdx(TankPartNames.GUN), typeDescriptor.gun.hitTester.bspModelName))
        else:
            bspModels = ((TankPartNames.getIdx(TankPartNames.CHASSIS), typeDescriptor.chassis.hitTester.bspModelName), (TankPartNames.getIdx(TankPartNames.HULL), typeDescriptor.hull.hitTester.bspModelName))
        collisionAssembler = BigWorld.CollisionAssembler(bspModels, self.worldID)
        prereqs.append(collisionAssembler)
        physicalTracksBuilders = self.typeDescriptor.chassis.physicalTracks
        for name, builders in physicalTracksBuilders.iteritems():
            for index, builder in enumerate(builders):
                prereqs.append(builder.createLoader(self.worldID, '{0}{1}PhysicalTrack'.format(name, index), modelsSetParams.skin))

        return prereqs

    def construct(self, isPlayer, resourceRefs):
        self.collisions = resourceRefs['collisionAssembler']
        self.typeDescriptor.chassis.hitTester.bbox = self.collisions.getBoundingBox(TankPartNames.getIdx(TankPartNames.CHASSIS))
        self.typeDescriptor.hull.hitTester.bbox = self.collisions.getBoundingBox(TankPartNames.getIdx(TankPartNames.HULL))
        self.typeDescriptor.turret.hitTester.bbox = self.collisions.getBoundingBox(TankPartNames.getIdx(TankPartNames.TURRET))
        self.typeDescriptor.gun.hitTester.bbox = self.collisions.getBoundingBox(TankPartNames.getIdx(TankPartNames.GUN))
        self.__isObserver = 'observer' in self.typeDescriptor.type.tags
        self._compoundModel = resourceRefs[self.typeDescriptor.name]
        self.__boundEffects = bound_effects.ModelBoundEffects(self.compoundModel)
        isCurrentModelDamaged = self.damageState.isCurrentModelDamaged
        fashions = camouflages.prepareFashions(isCurrentModelDamaged)
        if not isCurrentModelDamaged:
            model_assembler.setupTracksFashion(self.typeDescriptor, fashions.chassis)
        self._setFashions(fashions, self.isTurretDetached)
        self._setupModels()
        if not isCurrentModelDamaged:
            modelsSet = self.outfit.modelsSet
            self._splineTracks = model_assembler.setupSplineTracks(self.fashion, self.typeDescriptor, self.compoundModel, resourceRefs, modelsSet)
            self.crashedTracksController = CrashedTrackController(self.typeDescriptor, self.fashion, modelsSet)
        else:
            self.__trackScrollCtl = None
        self._chassisDecal.create()
        self.__modelAnimators = camouflages.getModelAnimators(self.outfit, self.typeDescriptor, self.worldID, resourceRefs, self.compoundModel)
        if self.modelsSetParams.state == 'undamaged':
            self.__modelAnimators.extend(camouflages.getAttachmentsAnimators(self.__attachments, self.worldID, resourceRefs, self.compoundModel))
        self.transform = self.createComponent(GenericComponents.TransformComponent, Math.Vector3(0, 0, 0))
        self.areaTriggerTarget = self.createComponent(Triggers.AreaTriggerTarget)
        self.__filter = model_assembler.createVehicleFilter(self.typeDescriptor)
        compoundModel = self.compoundModel
        if self.isAlive:
            self.detailedEngineState, self.gearbox = model_assembler.assembleDrivetrain(self, isPlayer)
            if not gEffectsDisabled():
                self.customEffectManager = CustomEffectManager(self)
                if self.typeDescriptor.hasSiegeMode:
                    self.siegeEffects = SiegeEffectsController(self, isPlayer)
                model_assembler.assembleVehicleAudition(isPlayer, self)
                self.detailedEngineState.onEngineStart = self._onEngineStart
                self.detailedEngineState.onStateChanged = self.engineAudition.onEngineStateChanged
            if isPlayer:
                turret = self.typeDescriptor.turret
                gunRotatorAudition = self.createComponent(Vehicular.GunRotatorAudition, turret.turretRotatorSoundManual, turret.weight / 1000.0, compoundModel.node(TankPartNames.TURRET))
                gunRotatorAudition.vehicleMatrixLink = self.compoundModel.root
                gunRotatorAudition.damaged = lambda : self.turretDamaged()
                gunRotatorAudition.maxTurretRotationSpeed = lambda : self.maxTurretRotationSpeed()
                self.gunRotatorAudition = gunRotatorAudition
                self.frictionAudition = self.createComponent(Vehicular.FrictionAudition, TANK_FRICTION_EVENT)
        isLodTopPriority = isPlayer
        lodCalcInst = self.createComponent(Vehicular.LodCalculator, DataLinks.linkMatrixTranslation(compoundModel.matrix), True, VEHICLE_PRIORITY_GROUP, isLodTopPriority)
        self.lodCalculator = lodCalcInst
        self.allLodCalculators.append(lodCalcInst)
        lodLink = DataLinks.createFloatLink(lodCalcInst, 'lodDistance')
        lodStateLink = lodCalcInst.lodStateLink
        if IS_EDITOR:
            matrixBinding = None
            changeCamera = None
        else:
            matrixBinding = BigWorld.player().consistentMatrices.onVehicleMatrixBindingChanged
            changeCamera = BigWorld.player().inputHandler.onCameraChanged
        self.shadowManager = VehicleShadowManager(compoundModel, matrixBinding, changeCamera)
        if not self.damageState.isCurrentModelDamaged:
            self.__assembleNonDamagedOnly(resourceRefs, isPlayer, lodLink, lodStateLink)
            dirtEnabled = BigWorld.WG_dirtEnabled() and 'HD' in self.typeDescriptor.type.tags
            if dirtEnabled and self.fashions is not None:
                dirtHandlers = [BigWorld.PyDirtHandler(True, compoundModel.node(TankPartNames.CHASSIS).position.y),
                 BigWorld.PyDirtHandler(False, compoundModel.node(TankPartNames.HULL).position.y),
                 BigWorld.PyDirtHandler(False, compoundModel.node(TankPartNames.TURRET).position.y),
                 BigWorld.PyDirtHandler(False, compoundModel.node(TankPartNames.GUN).position.y)]
                modelHeight, _ = self.computeVehicleHeight()
                self.dirtComponent = self.createComponent(Vehicular.DirtComponent, dirtHandlers, modelHeight)
                for fashionIdx, _ in enumerate(TankPartNames.ALL):
                    self.fashions[fashionIdx].addMaterialHandler(dirtHandlers[fashionIdx])
                    self.fashions[fashionIdx].addTrackMaterialHandler(dirtHandlers[fashionIdx])

        model_assembler.setupTurretRotations(self)
        self.waterSensor = model_assembler.assembleWaterSensor(self.typeDescriptor, self, lodStateLink, self.worldID)
        if self.engineAudition is not None:
            self.engineAudition.setIsUnderwaterInfo(DataLinks.createBoolLink(self.waterSensor, 'isUnderWater'))
            self.engineAudition.setIsInWaterInfo(DataLinks.createBoolLink(self.waterSensor, 'isInWater'))
        self.__postSetupFilter()
        compoundModel.setPartBoundingBoxAttachNode(TankPartIndexes.GUN, TankNodeNames.GUN_INCLINATION)
        camouflages.updateFashions(self)
        model_assembler.assembleCustomLogicComponents(self, self.__attachments, self.__modelAnimators)
        return

    def destroy(self):
        self.flagComponent = None
        self.__modelAnimators = []
        self._destroySystems()
        fashions = VehiclePartsTuple(None, None, None, None)
        self._setFashions(fashions, self._isTurretDetached)
        super(CommonTankAppearance, self).destroy()
        self.__typeDesc = None
        if self.boundEffects is not None:
            self.boundEffects.destroy()
        self._vehicleStickers = None
        self._chassisDecal.destroy()
        self._chassisDecal = None
        self._compoundModel = None
        return

    def activate(self):
        if self.collisions is not None and self.isTurretDetached:
            self.collisions.removeAttachment(TankPartNames.getIdx(TankPartNames.TURRET))
            self.collisions.removeAttachment(TankPartNames.getIdx(TankPartNames.GUN))
        super(CommonTankAppearance, self).activate()
        if not self.isObserver:
            self._chassisDecal.attach()
        self._createAndAttachStickers()
        if not self.isObserver:
            if not self.damageState.isCurrentModelDamaged and not self.__systemStarted:
                self._startSystems()
            self.filter.enableLagDetection(not self.damageState.isCurrentModelDamaged)
            if self.__periodicTimerID is not None:
                BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = BigWorld.callback(PERIODIC_UPDATE_TIME, self.__onPeriodicTimer)
        self.setupGunMatrixTargets(self.filter)
        for lodCalculator in self.allLodCalculators:
            lodCalculator.setupPosition(DataLinks.linkMatrixTranslation(self.compoundModel.matrix))

        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.start()

        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.isObserver:
            self.compoundModel.visible = False
        if self.collisions is not None:
            chassisColisionMatrix, gunNodeName = self._vehicleColliderInfo
            collisionData = ((TankPartNames.getIdx(TankPartNames.HULL), self.compoundModel.node(TankPartNames.HULL)),
             (TankPartNames.getIdx(TankPartNames.TURRET), self.compoundModel.node(TankPartNames.TURRET)),
             (TankPartNames.getIdx(TankPartNames.CHASSIS), chassisColisionMatrix),
             (TankPartNames.getIdx(TankPartNames.GUN), self.compoundModel.node(gunNodeName)))
            self.collisions.connect(self.id, ColliderTypes.VEHICLE_COLLIDER, collisionData)
        return

    def deactivate(self):
        for modelAnimator in self.__modelAnimators:
            modelAnimator.animator.stop()

        if self.damageState and self.damageState.isCurrentModelDamaged:
            self.__modelAnimators = []
        self.shadowManager.unregisterCompoundModel(self.compoundModel)
        if self.__systemStarted:
            self._stopSystems()
        super(CommonTankAppearance, self).deactivate()
        self._chassisDecal.detach()
        self.filter.enableLagDetection(False)
        if self.vehicleStickers:
            self.vehicleStickers.detach()

    def setupGunMatrixTargets(self, target):
        self.turretMatrix = target.turretMatrix
        self.gunMatrix = target.gunMatrix

    def receiveShotImpulse(self, direction, impulse):
        if not VehicleDamageState.isDamagedModel(self.damageState.modelState):
            self.swingingAnimator.receiveShotImpulse(direction, impulse)
            if self.crashedTracksController is not None:
                self.crashedTracksController.receiveShotImpulse(direction, impulse)
        return

    def computeVehicleHeight(self):
        gunLength = 0.0
        height = 0.0
        if self.collisions is not None:
            desc = self.typeDescriptor
            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 onWaterSplash(self, waterHitPoint, isHeavySplash):
        pass

    def onUnderWaterSwitch(self, isUnderWater):
        pass

    def getWheelsSteeringMax(self):
        pass

    def _prepareOutfit(self, outfitCD):
        outfitComponent = camouflages.getOutfitComponent(outfitCD)
        return Outfit(component=outfitComponent, vehicleCD=self.typeDescriptor.makeCompactDescr())

    def _setupModels(self):
        self.__isAlive = not self.damageState.isCurrentModelDamaged
        if self.isAlive:
            _, gunLength = self.computeVehicleHeight()
            self.__weaponEnergy = gunLength * self.typeDescriptor.shot.shell.caliber
        if MAX_DISTANCE > 0 and not self.isObserver:
            transform = self.typeDescriptor.chassis.AODecals[0]
            splodge = BigWorld.Splodge(transform, MAX_DISTANCE, self.typeDescriptor.chassis.hullPosition.y)
            if splodge:
                self.__splodge = splodge
                node = self.compoundModel.node(TankPartNames.HULL)
                node.attach(splodge)

    def _createStickers(self):
        return VehicleStickers(self.typeDescriptor, 0, self.outfit)

    @property
    def _vehicleColliderInfo(self):
        chassisColisionMatrix = self.compoundModel.matrix
        if self.damageState.isCurrentModelDamaged:
            gunNodeName = 'gun'
        else:
            gunNodeName = TankNodeNames.GUN_INCLINATION
        return (chassisColisionMatrix, gunNodeName)

    def _startSystems(self):
        self.__systemStarted = True
        if self.flyingInfoProvider is not None:
            self.flyingInfoProvider.setData(self.filter, self.suspension)
        if self.trackScrollController is not None:
            self.trackScrollController.activate()
            self.trackScrollController.setData(self.filter)
        if self.engineAudition is not None:
            self.engineAudition.setWeaponEnergy(self._weaponEnergy)
            self.engineAudition.attachToModel(self.compoundModel)
        if self.hullAimingController is not None:
            self.hullAimingController.setData(self.filter, self.typeDescriptor)
        return

    def _stopSystems(self):
        self.__systemStarted = False
        if self.flyingInfoProvider is not None:
            self.flyingInfoProvider.setData(None, None)
        if self.trackScrollController is not None:
            self.trackScrollController.deactivate()
            self.trackScrollController.setData(None)
        if self.__periodicTimerID is not None:
            BigWorld.cancelCallback(self.__periodicTimerID)
            self.__periodicTimerID = None
        return

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

    def _onRequestModelsRefresh(self):
        self.flagComponent = None
        return

    def _onEngineStart(self):
        if self.engineAudition is not None:
            self.engineAudition.onEngineStart()
        return

    def __assembleNonDamagedOnly(self, resourceRefs, isPlayer, lodLink, lodStateLink):
        model_assembler.assembleTerrainMatKindSensor(self, lodStateLink, self.worldID)
        model_assembler.assembleRecoil(self, lodLink)
        model_assembler.assembleMultiGunRecoil(self, lodLink)
        model_assembler.assembleGunLinkedNodesAnimator(self)
        model_assembler.assembleCollisionObstaclesCollector(self, lodStateLink, self.typeDescriptor, self.worldID)
        model_assembler.assembleTessellationCollisionSensor(self, lodStateLink)
        wheelsScroll = None
        wheelsSteering = None
        generalWheelsAnimatorConfig = self.typeDescriptor.chassis.generalWheelsAnimatorConfig
        if generalWheelsAnimatorConfig is not None:
            scrollableWheelsCount = generalWheelsAnimatorConfig.getNonTrackWheelsCount()
            wheelsScroll = []
            for _ in xrange(scrollableWheelsCount):
                retriever = self.createComponent(NetworkFilters.FloatFilterRetriever)
                wheelsScroll.append(DataLinks.createFloatLink(retriever, 'value'))
                self.filterRetrievers.append(retriever)

            steerableWheelsCount = generalWheelsAnimatorConfig.getSteerableWheelsCount()
            wheelsSteering = []
            for _ in xrange(steerableWheelsCount):
                retriever = self.createComponent(NetworkFilters.FloatFilterRetriever)
                wheelsSteering.append(DataLinks.createFloatLink(retriever, 'value'))
                self.filterRetrievers.append(retriever)

        self.wheelsAnimator = model_assembler.createWheelsAnimator(self, ColliderTypes.VEHICLE_COLLIDER, self.typeDescriptor, lambda : self.wheelsState, wheelsScroll, wheelsSteering, self.splineTracks, lodStateLink)
        if self.customEffectManager is not None:
            self.customEffectManager.setWheelsData(self)
        suspensionLodLink = lodStateLink
        if 'wheeledVehicle' in self.typeDescriptor.type.tags:
            wheeledLodCalculator = Vehicular.LodCalculator(self.worldID, DataLinks.linkMatrixTranslation(self.compoundModel.matrix), True, WHEELED_CHASSIS_PRIORITY_GROUP, isPlayer)
            self.wheeledLodCalculator = wheeledLodCalculator
            self.allLodCalculators.append(wheeledLodCalculator)
            suspensionLodLink = wheeledLodCalculator.lodStateLink
        model_assembler.assembleSuspensionIfNeed(self, suspensionLodLink)
        model_assembler.assembleLeveredSuspensionIfNeed(self, suspensionLodLink)
        self.__assembleSwinging(lodLink)
        model_assembler.assembleBurnoutProcessor(self)
        model_assembler.assembleSuspensionSound(self, lodLink, isPlayer)
        model_assembler.assembleHullAimingController(self)
        self.trackNodesAnimator = model_assembler.createTrackNodesAnimator(self, self.typeDescriptor, lodStateLink)
        model_assembler.assembleTracks(resourceRefs, self.typeDescriptor, self, self.splineTracks, False, lodStateLink)
        model_assembler.assembleVehicleTraces(self, self.filter, lodStateLink)
        return

    def __assembleSwinging(self, lodLink):
        self.swingingAnimator = model_assembler.createSwingingAnimator(self, self.typeDescriptor, self.compoundModel.node(TankPartNames.HULL).localMatrix, self.compoundModel.matrix, lodLink)
        self.compoundModel.node(TankPartNames.HULL, self.swingingAnimator.animatedMProv)
        if hasattr(self.filter, 'placingCompensationMatrix'):
            self.swingingAnimator.placingCompensationMatrix = self.filter.placingCompensationMatrix

    def __postSetupFilter(self):
        suspensionWorking = self.suspension is not None and self.suspension.hasGroundNodes
        placingOnGround = not (suspensionWorking or self.leveredSuspension is not None)
        self.filter.placingOnGround = placingOnGround
        return

    def _createAndAttachStickers(self):
        isCurrentModelDamaged = self.damageState.isCurrentModelDamaged
        stickersAlpha = DEFAULT_STICKERS_ALPHA
        if isCurrentModelDamaged:
            stickersAlpha = items.vehicles.g_cache.commonConfig['miscParams']['damageStickerAlpha']
        if self.vehicleStickers is None:
            self._vehicleStickers = self._createStickers()
        self.vehicleStickers.alpha = stickersAlpha
        self.vehicleStickers.attach(compoundModel=self.compoundModel, isDamaged=self.damageState.isCurrentModelDamaged, showDamageStickers=not isCurrentModelDamaged)
        return

    def __onPeriodicTimer(self):
        timeStamp = BigWorld.wg_getFrameTimestamp()
        if self.__frameTimestamp >= timeStamp:
            self.__periodicTimerID = BigWorld.callback(0.0, self.__onPeriodicTimer)
        else:
            self.__frameTimestamp = timeStamp
            self.__periodicTimerID = BigWorld.callback(PERIODIC_UPDATE_TIME, self.__onPeriodicTimer)
            self._periodicUpdate()

    def _periodicUpdate(self):
        if self._vehicle is None or not self._vehicle.isAlive():
            return
        else:
            self._updateCurrTerrainMatKinds()
            self.__updateEffectsLOD()
            if self.customEffectManager:
                self.customEffectManager.update()
            return

    def __updateEffectsLOD(self):
        if self.customEffectManager:
            distanceFromPlayer = self.lodCalculator.lodDistance
            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 _stopEffects(self):
        self.boundEffects.stop()

    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)
            args = dict(isPlayerVehicle=vehicle.isPlayerVehicle, showShockWave=vehicle.isPlayerVehicle, showFlashBang=vehicle.isPlayerVehicle, entity_id=vehicle.id, 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))
            if isSpawnedBot(self.typeDescriptor.type.tags) and kind in ('explosion', 'destruction'):
                player = BigWorld.player()
                if player is not None and isPlayerAvatar():
                    player.terrainEffects.addNew(self._vehicle.position, effects[1], effects[0], None, **args)
            else:
                self.boundEffects.addNew(None, effects[1], effects[0], **args)
            return

    def _updateCurrTerrainMatKinds(self):
        if self.terrainMatKindSensor is None:
            return
        else:
            matKinds = self.terrainMatKindSensor.matKinds
            groundTypes = self.terrainMatKindSensor.groundTypes
            materialsCount = len(matKinds)
            for i in xrange(MATKIND_COUNT):
                matKind = matKinds[i] if i < materialsCount else 0
                groundType = groundTypes[i] if i < materialsCount else 0
                self.terrainMatKind[i] = matKind
                self.terrainGroundType[i] = groundType
                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.terrainMatKind[0], self.terrainMatKind[1])
            return

    def changeEngineMode(self, mode, forceSwinging=False):
        if self.detailedEngineState is not None:
            self.detailedEngineState.mode = mode[0]
        if self.trackScrollController is not None:
            self.trackScrollController.setMode(mode)
        return

    def changeSiegeState(self, siegeState):
        if self.engineAudition is not None:
            self.engineAudition.onSiegeStateChanged(siegeState)
        return

    def turretDamaged(self):
        pass

    def maxTurretRotationSpeed(self):
        pass
Пример #10
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)