Example #1
0
class VehicleStateController(IBattleController):

    def __init__(self):
        super(VehicleStateController, self).__init__()
        self.__eManager = Event.EventManager()
        self.onVehicleStateUpdated = Event.Event(self.__eManager)
        self.onVehicleControlling = Event.Event(self.__eManager)
        self.onPostMortemSwitched = Event.Event(self.__eManager)
        self.onRespawnBaseMoving = Event.Event(self.__eManager)
        self.__cachedStateValues = {}
        self.__waitingTI = TimeInterval(VEHICLE_WAINING_INTERVAL, self, '_waiting')
        self.__vehicleID = 0
        self.__updater = None
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        self.__needInvalidate = False
        return

    def getControllerID(self):
        return BATTLE_CTRL_ID.OBSERVED_VEHICLE_STATE

    def startControl(self):
        pass

    def stopControl(self):
        if self.__waitingTI is not None:
            self.__waitingTI.stop()
            self.__waitingTI = None
        if self.__updater is not None:
            self.__updater.clear()
            self.__updater = None
        self.__vehicleID = 0
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        self.__eManager.clear()
        self.__cachedStateValues.clear()
        return

    @property
    def isInPostmortem(self):
        return self.__isInPostmortem

    def setPlayerVehicle(self, vehicleID):
        self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, vehicleID)
        self.__vehicleID = vehicleID
        self.__updater = _VehicleUpdater(self, self.__vehicleID)
        self.__waitingTI.start()

    def getControllingVehicle(self):
        vehicle = None
        if self.__vehicleID:
            vehicle = BigWorld.entity(self.__vehicleID)
        return vehicle

    def getControllingVehicleID(self):
        return self.__vehicleID

    def notifyStateChanged(self, stateID, value):
        if stateID == VEHICLE_VIEW_STATE.DEVICES:
            self.__cachedStateValues.setdefault(stateID, [])
            self.__cachedStateValues[stateID].append(value)
        else:
            self.__cachedStateValues[stateID] = value
        self.onVehicleStateUpdated(stateID, value)

    def getStateValue(self, stateID):
        return self.__cachedStateValues[stateID] if stateID in self.__cachedStateValues else None

    def refreshVehicleStateValue(self, stateID):
        if stateID in self.__cachedStateValues:
            self.onVehicleStateUpdated(stateID, self.__cachedStateValues[stateID])

    def invalidate(self, state, value, vehicleID=0):
        if vehicleID != 0 and vehicleID != self.__vehicleID:
            return
        else:
            isStateChangeHandled = False
            if self.__isRqToSwitch and self.__waitingTI.isStarted():
                self._waiting()
            if self.__updater is not None:
                isStateChangeHandled = self.__updater.handleStateChange(state, value)
            if not isStateChangeHandled:
                self.notifyStateChanged(state, value)
            return

    def switchToPostmortem(self, noRespawnPossible, respawnAvailable):
        self.__isRqToSwitch = False
        if avatar_getter.getPlayerVehicleID() == self.__vehicleID:
            if self.__updater is not None:
                self.__updater.stop()
                self.__updater.updateOnce()
        self.__isInPostmortem = True
        self.onPostMortemSwitched(noRespawnPossible, respawnAvailable)
        return

    def switchToOther(self, vehicleID):
        if vehicleID is None:
            self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, 0)
            self.__needInvalidate = True
            return
        elif self.__vehicleID == vehicleID and not self.__needInvalidate:
            return
        else:
            self.__needInvalidate = False
            self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, vehicleID)
            self.__waitingTI.stop()
            if self.__updater:
                self.__updater.stop()
            self.__vehicleID = vehicleID
            self.__isRqToSwitch = True
            self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, self.__vehicleID)
            self.__cachedStateValues.clear()
            self.__waitingTI.start()
            return

    def movingToRespawn(self):
        self.__isInPostmortem = False
        self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, 0)
        self.onRespawnBaseMoving()
        self.__cachedStateValues.clear()

    def _waiting(self):
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None and vehicle.isStarted:
            self.__waitingTI.stop()
            self._setup(vehicle)
        return

    def _setup(self, vehicle):
        if self.__updater is not None:
            self.__updater = self.__updater.switch(self.__vehicleID)
        if self.__isRqToSwitch:
            nationID = vehicle.typeDescriptor.type.id[0]
            notifications = avatar_getter.getSoundNotifications()
            if notifications is not None:
                notifications.clear()
            SoundGroups.g_instance.soundModes.setCurrentNation(nations.NAMES[nationID])
        self.onVehicleControlling(vehicle)
        if self.__updater is not None:
            self.__updater.start()
        return
Example #2
0
class _VehicleUpdater(object):
    """
    The class implements logic for tracking changes of vehicle parameters in equal time intervals.
    It encapsulates different algorithms of tracking based on vehicle type. The inner logic is
    based on the custom state handlers (classes derived from _StateHandler).
    """

    def __init__(self, vehCtrl, vehicleID):
        """
        Constructor. Initializes internal variables.
        
        :param vehCtrl: Reference to vehicle state controller owning this updater.
        :param vehicleID: Unique identifier of vehicle to be tracked
        """
        super(_VehicleUpdater, self).__init__()
        self.__isAlive = True
        self.__handlers = None
        self.__ctrl = weakref.proxy(vehCtrl)
        self.__vehicleID = vehicleID
        self.__updateTI = None
        self._setUpHandlers()
        return

    def handleStateChange(self, state, value):
        """
        Represents a hook to catch states changes before the vehicle state controller propagates
        them to subscribers. Redirects the call to each handler to perform required logic.
        
        :param state: A state listed within VEHICLE_VIEW_STATE
        :param value: State value
        :return: True if any handler returns True. Otherwise - False.
        """
        isStateHandled = False
        for handler in self.__handlers:
            isStateHandled |= handler.handleStateChange(state, value)

        return isStateHandled

    def switch(self, vehicleID):
        """
        Switches the updater to another vehicle: reset internal states and handlers.
        
        :param vehicleID: Unique identifier of a new vehicle that will be tracked
        """
        self.clear()
        self.__vehicleID = vehicleID
        self._setUpHandlers()
        return self

    def start(self):
        """
        Sets up and starts the timer to invalidate handlers.
        """
        if self.__updateTI is None:
            self.__updateTI = TimeInterval(VEHICLE_UPDATE_INTERVAL, self, '_update')
        self.__updateTI.start()
        return

    def stop(self):
        """
        Stops and clears running timer (if any).
        """
        if self.__updateTI is not None:
            self.__updateTI.stop()
            self.__updateTI = None
        return

    def clear(self):
        """
        Clears internal variables and stops timer.
        """
        self.stop()
        if self.__handlers is not None:
            for handler in self.__handlers:
                handler.clear()

            self.__handlers = None
        self.__vehicleID = None
        self.__isAlive = True
        return

    def updateOnce(self):
        """
        Invokes this routine to singe update of information about vehicle if it is needed.
        For example, player's vehicle is destroyed, we need to update modules and crew states, set max speed.
        """
        raise self.__updateTI is None or not self.__updateTI.isStarted() or AssertionError('Updater is started')
        self._update()
        return

    def notifyStateChanged(self, state, value):
        """
        The helper method to provide handler ability to notify subscribers about changes of
        vehicle's states through the vehicle state controller.
        
        :param state: A state listed within VEHICLE_VIEW_STATE
        :param value: State value.
        """
        self.__ctrl.notifyStateChanged(state, value)

    def _update(self):
        """
        Callback on timer tick event. Initiates invalidation of all handlers, get all states
        changes and redirects them to the vehicle state controller by emitting appropriate events.
        """
        states = []
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None and vehicle.isStarted:
            if not vehicle.isAlive() and self.__isAlive:
                self.__isAlive = False
                if vehicle.health > 0 and not vehicle.isCrewActive:
                    states.append((VEHICLE_VIEW_STATE.CREW_DEACTIVATED, 0))
                else:
                    states.append((VEHICLE_VIEW_STATE.DESTROYED, 0))
            for handler in self.__handlers:
                newStates = handler(vehicle)
                states.extend(newStates)

        for item in states:
            self.notifyStateChanged(*item)

        return

    def _setUpHandlers(self):
        """
        Determines a list of handlers based on vehicle type and sets up them.
        """
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None:
            isPlayerVehicle = vehicle.isPlayerVehicle
            if isPlayerVehicle:
                if not vehicle.isAlive():
                    self.__handlers = (_SpeedStateHandler(self, True),)
                else:
                    self.__handlers = (_SpeedStateHandler(self, True), _RpmStateHandler(self, vehicle), _EngineStateHandler(self, vehicle))
            else:
                self.__handlers = (_HealthStateHandler(self),
                 _SpeedStateHandler(self, False),
                 _RpmStateHandler(self, vehicle),
                 _EngineStateHandler(self, vehicle))
        return
Example #3
0
class VehicleStateController(IBattleController):
    """
    Class of controller that tracks specified vehicle on arena. At any time there is only one
    instance of this class. When player's own vehicle is destroyed, controller can track other
    vehicles on player's team.
    The main goal of the controller to combine all knowledge about vehicle state changes and to
    notify subscribers about that through appropriate events. The controller declares public
    interface to know about changes. Triggers call these method to notify the controller about
    specific changes that occur outside client. Also there is a list of changes that are triggered
    directly by the controller via the updater. The updater tracks those parameters on which there
    are no outside triggers or when required to control triggering frequency.
    """

    def __init__(self):
        """
        Constructor. Initializes internal variables.
        """
        super(VehicleStateController, self).__init__()
        self.__eManager = Event.EventManager()
        self.onVehicleStateUpdated = Event.Event(self.__eManager)
        self.onVehicleControlling = Event.Event(self.__eManager)
        self.onPostMortemSwitched = Event.Event(self.__eManager)
        self.onRespawnBaseMoving = Event.Event(self.__eManager)
        self.__cachedStateValues = {}
        self.__waitingTI = TimeInterval(VEHICLE_WAINING_INTERVAL, self, '_waiting')
        self.__vehicleID = 0
        self.__updater = None
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        return

    def getControllerID(self):
        return BATTLE_CTRL_ID.OBSERVED_VEHICLE_STATE

    def startControl(self):
        pass

    def stopControl(self):
        """
        Clears internal variables and stops timers.
        :return:
        """
        if self.__waitingTI is not None:
            self.__waitingTI.stop()
            self.__waitingTI = None
        if self.__updater is not None:
            self.__updater.clear()
            self.__updater = None
        self.__vehicleID = 0
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        self.__eManager.clear()
        self.__cachedStateValues.clear()
        return

    @property
    def isInPostmortem(self):
        return self.__isInPostmortem

    def setPlayerVehicle(self, vehicleID):
        """
        Sets ID of player's own vehicle.
        
        :param vehicleID: ID of vehicle entity
        """
        self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, vehicleID)
        self.__vehicleID = vehicleID
        self.__updater = _VehicleUpdater(self, self.__vehicleID)
        self.__waitingTI.start()

    def getControllingVehicle(self):
        """
        Gets entity of vehicle that is controlling.
        
        :return: entity of vehicle or None
        """
        vehicle = None
        if self.__vehicleID:
            vehicle = BigWorld.entity(self.__vehicleID)
        return vehicle

    def getControllingVehicleID(self):
        return self.__vehicleID

    def notifyStateChanged(self, stateID, value):
        """
        Store the specified state value, and raise the event
        :param stateID: id for state, listed within VEHICLE_VIEW_STATE
        :param value: state value
        """
        if stateID == VEHICLE_VIEW_STATE.DEVICES:
            self.__cachedStateValues.setdefault(stateID, [])
            self.__cachedStateValues[stateID].append(value)
        else:
            self.__cachedStateValues[stateID] = value
        self.onVehicleStateUpdated(stateID, value)

    def getStateValue(self, stateID):
        """
        Returns the last value for the the desired state
        :param stateID: id of state, listed within VEHICLE_VIEW_STATE
        :return: None or state value
        """
        if stateID in self.__cachedStateValues:
            return self.__cachedStateValues[stateID]
        else:
            return None

    def refreshVehicleStateValue(self, stateID):
        """
        Re-calls Update on the cached value of the vehicle state, nothing if not cached
        :param stateID: id of state, listed within VEHICLE_VIEW_STATE
        """
        if stateID in self.__cachedStateValues:
            self.onVehicleStateUpdated(stateID, self.__cachedStateValues[stateID])

    def invalidate(self, state, value, vehicleID = 0):
        """
        Invalidates vehicle sates. The method should be invoked by vehicle state change trigger.
        
        :param state: A state listed within VEHICLE_VIEW_STATE
        :param value: New state value
        :param vehicleID: ID of vehicle for which the given state is changed.
        """
        if vehicleID != 0 and vehicleID != self.__vehicleID:
            return
        else:
            isStateChangeHandled = False
            if self.__isRqToSwitch and self.__waitingTI.isStarted():
                self._waiting()
            if self.__updater is not None:
                isStateChangeHandled = self.__updater.handleStateChange(state, value)
            if not isStateChangeHandled:
                self.notifyStateChanged(state, value)
            return

    def switchToPostmortem(self, noRespawnPossible, respawnAvailable):
        """
        Switches to postmortem mode.
        :param leave:
        """
        self.__isRqToSwitch = False
        if avatar_getter.getPlayerVehicleID() == self.__vehicleID:
            if self.__updater is not None:
                self.__updater.stop()
                self.__updater.updateOnce()
        self.__isInPostmortem = True
        self.onPostMortemSwitched(noRespawnPossible, respawnAvailable)
        return

    def switchToOther(self, vehicleID):
        """
        Switches to another vehicle.
        
        :param vehicleID: ID of a new vehicle to be tracked.
        """
        if self.__vehicleID == vehicleID or vehicleID is None:
            return
        else:
            self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, vehicleID)
            self.__waitingTI.stop()
            if self.__updater:
                self.__updater.stop()
            self.__vehicleID = vehicleID
            self.__isRqToSwitch = True
            self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, self.__vehicleID)
            self.__cachedStateValues.clear()
            self.__waitingTI.start()
            return

    def movingToRespawn(self):
        """
        Emits the appropriate event and state change event. The method should be called
        when the vehicle is moved to respawn.
        :return:
        """
        self.__isInPostmortem = False
        self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, 0)
        self.onRespawnBaseMoving()
        self.__cachedStateValues.clear()

    def _waiting(self):
        """
        Callback to be invoked when the waiting timer is elapsed. Starts the updater when the
        avatar has been switched to the required vehicle.
        """
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None and vehicle.isStarted:
            self.__waitingTI.stop()
            self._setup(vehicle)
        return

    def _setup(self, vehicle):
        """
        Set up the updater and other properties
        :param vehicle: Vehicle entity.
        """
        if self.__updater is not None:
            self.__updater = self.__updater.switch(self.__vehicleID)
        if self.__isRqToSwitch:
            nationID = vehicle.typeDescriptor.type.id[0]
            notifications = avatar_getter.getSoundNotifications()
            if notifications is not None:
                notifications.clear()
            SoundGroups.g_instance.soundModes.setCurrentNation(nations.NAMES[nationID])
        self.onVehicleControlling(vehicle)
        if self.__updater is not None:
            self.__updater.start()
        return
Example #4
0
class BaseMarkerController(IArenaVehiclesController):
    _UPDATE_TICK_LENGTH = 0.01

    def __init__(self):
        super(BaseMarkerController, self).__init__()
        self.onTickUpdate = Event.Event()
        self._gui = None
        self._attachGUIToMarkersCallback = {}
        self._markers = {}
        self._updateTI = None
        self._globalVisibility = True
        return

    def init(self):
        _logger.debug('BaseMarkerController.init')
        self._gui = MarkerGUIProvider(self.getPluginID())
        g_eventBus.addListener(events.GameEvent.GUI_VISIBILITY,
                               self._handleGUIVisibility,
                               scope=EVENT_BUS_SCOPE.BATTLE)

    @property
    def allMarkers(self):
        return self._markers.values()

    @property
    def allMarkersID(self):
        return self._markers.keys()

    def getPluginID(self):
        raise NotImplementedError

    def createMarker(self, matrix, markerType, clazz=AreaMarker, bitMask=0):
        markerData = MarkerParamsFactory.getMarkerParams(
            matrix, markerType, bitMask)
        return clazz(markerData)

    def addMarker(self, marker):
        markerID = marker.markerID
        if markerID in self._markers:
            _logger.error('Marker with Id=%s exists already', markerID)
            marker.clear()
            return None
        else:
            self._attachGUIToMarkersCallback[markerID] = BigWorld.callback(
                0, partial(self._attachGUIToMarkers, markerID))
            self._checkGlobalVisibilityForMarker(marker)
            self._markers[markerID] = marker
            self.checkStartTimer()
            return markerID

    def setMarkerMatrix(self, markerID, matrix):
        marker = self._markers.get(markerID, None)
        if marker:
            marker.setMatrix(matrix)
        return

    def removeMarker(self, markerID):
        if markerID not in self._markers:
            return
        else:
            if markerID in self._attachGUIToMarkersCallback:
                BigWorld.cancelCallback(
                    self._attachGUIToMarkersCallback[markerID])
                self._attachGUIToMarkersCallback.pop(markerID)
            else:
                self._markers[markerID].detachGUI()
            self._markers[markerID].clear()
            self._markers.pop(markerID, None)
            return

    def removeAllMarkers(self):
        for markerID in self._markers:
            if markerID in self._attachGUIToMarkersCallback:
                BigWorld.cancelCallback(
                    self._attachGUIToMarkersCallback[markerID])
                self._attachGUIToMarkersCallback.pop(markerID)
            else:
                self._markers[markerID].detachGUI()
            self._markers[markerID].clear()

        self._markers.clear()

    def showMarkers(self, unblock=True):
        if not self._globalVisibility:
            return
        for markerID in self._markers.iterkeys():
            self.showMarkersById(markerID, unblock)

        self.checkStartTimer()

    def hideMarkers(self, block=True):
        for marker in self._markers.values():
            marker.setVisible(False)
            if block:
                marker.blockChangVisibility = True

        if self._updateTI is not None:
            self._updateTI.stop()
        return

    def showMarkersById(self, markerID, unblock=True):
        player = BigWorld.player()
        marker = self._markers.get(markerID, None)
        if marker:
            if unblock:
                marker.blockChangVisibility = False
            if not self._globalVisibility:
                return
            if marker.isEmpty():
                return
            conditionDistance = marker.disappearingRadius
            if conditionDistance > 0:
                distanceToArea = BaseMarkerController.getDistanceToArea(
                    marker, player)
                hide = conditionDistance < distanceToArea if marker.reverseDisappearing else conditionDistance > distanceToArea
                if hide:
                    marker.setVisible(False)
                    return
            marker.setVisible(True)
        self.checkStartTimer()
        return

    def hideMarkersById(self, markerID, block=True):
        if markerID in self._markers.keys():
            marker = self._markers[markerID]
            marker.setVisible(False)
            if block:
                marker.blockChangVisibility = True

    def getMarkerById(self, markerID):
        return self._markers.get(markerID)

    @staticmethod
    def getDistanceToArea(marker, player):
        absDistance = (marker.getMarkerPosition() -
                       player.getOwnVehiclePosition()).length
        distanceToArea = max(0, absDistance - marker.areaRadius)
        return distanceToArea

    def start(self):
        if self._gui is None:
            return
        else:
            if self._updateTI:
                self._updateTI.stop()
            for markerID in self._markers.iterkeys():
                if markerID not in self._attachGUIToMarkersCallback:
                    self._attachGUIToMarkersCallback[
                        markerID] = BigWorld.callback(
                            0.0,
                            partial(self._attachGUIToMarkers,
                                    markerID=markerID))

            self._updateTI = TimeInterval(self._UPDATE_TICK_LENGTH, self,
                                          '_tickUpdate')
            self._updateTI.start()
            return

    def checkStartTimer(self):
        if self._markers and self._updateTI and not self._updateTI.isStarted():
            self.start()

    def stop(self):
        if self._updateTI is not None:
            self._updateTI.stop()
            self._updateTI = None
        self._clear()
        return

    def checkStopTimer(self):
        if not self._markers and self._updateTI and self._updateTI.isStarted():
            self._updateTI.stop()

    def _tickUpdate(self):
        self.onTickUpdate()
        self.checkStopTimer()

    def _clear(self):
        g_eventBus.removeListener(events.GameEvent.GUI_VISIBILITY,
                                  self._handleGUIVisibility,
                                  scope=EVENT_BUS_SCOPE.BATTLE)
        self.removeAllMarkers()
        self._gui = None
        return

    def _attachGUIToMarkers(self, markerID):
        self._attachGUIToMarkersCallback[markerID] = None
        if self._gui and markerID in self._markers:
            marker = self._markers[markerID]
            if self._checkInitedPlugin(marker):
                self._attachGUIToMarkersCallback.pop(markerID)
                self._markers[markerID].attachGUI(self._gui)
                return
        self._attachGUIToMarkersCallback[markerID] = BigWorld.callback(
            0, partial(self._attachGUIToMarkers, markerID))
        return

    def _handleGUIVisibility(self, event):
        self._globalVisibility = event.ctx['visible']
        if self._globalVisibility:
            self.showMarkers(unblock=False)
        else:
            self.hideMarkers(block=False)

    def _checkInitedPlugin(self, marker):
        if marker.hasMarker2D() and self._gui.getMarkers2DPlugin() is None:
            return False
        else:
            return False if marker.hasMinimap(
            ) and self._gui.getMinimapPlugin() is None else True

    def _checkGlobalVisibilityForMarker(self, marker):
        if not self._globalVisibility:
            marker.setVisible(False)
Example #5
0
class _EngineStateHandler(_StateHandler):
    """
    The class provides logic to track engine state changes and vehicle movement changes.
    All logic is based on changes of the current gear.
    """
    __slots__ = ('__weakref__', '__gear', '__vehMoveAnimTimer', '__engineStartAnimTimer')

    def __init__(self, updater, vehicle):
        """
        Constructor. Initializes internal variables based on vehicle state.
        
        :param updater: An instance of _VehicleUpdater class, that creates this handler.
        :param vehicle: Vehicle to be tracked.
        :return:
        """
        super(_EngineStateHandler, self).__init__(updater)
        if vehicle is not None and vehicle.appearance is not None:
            self.__gear = vehicle.appearance.gear
        else:
            self.__gear = _STOPPED_ENGINE_GEAR
        self.__vehMoveAnimTimer = TimeInterval(_VEHICLE_ANIM_DURATION, self, '_stopVehMoveAnim')
        self.__engineStartAnimTimer = TimeInterval(_VEHICLE_ANIM_DURATION, self, '_stopEngineStartAnim')
        return

    def clear(self):
        """
        Resets internal variables.
        """
        self.__gear = _STOPPED_ENGINE_GEAR
        self._stopVehMoveAnim()
        self._stopEngineStartAnim()

    def _invalidate(self, vehicle):
        """
        The method determines whether one of the following events has occurred and exposes the
        appropriate state change to the updater:
            1. Vehicle starts moving.
            2. Engine is started.
        By these events, appropriate animations are run on UI. To stop animations appropriate state
        change event is sent in _VEHICLE_ANIM_DURATION time. Note that animation duration depends
        on sound effect (track length). But right now there is no way to determine effect duration.
        Therefore for all vehicles the same constant is used.
        
        :param vehicle: A reference to the tracked vehicle(BW entity).
        :return: The state change if any. Otherwise an empty list.
        """
        states = []
        if vehicle.appearance is not None:
            gear = vehicle.appearance.gear
            if self.__gear != gear:
                if not self.__gear and gear > 0:
                    if not self.__vehMoveAnimTimer.isStarted():
                        states.append((VEHICLE_VIEW_STATE.VEHICLE_MOVEMENT_STATE, True))
                        self.__vehMoveAnimTimer.start()
                elif self.__gear == _STOPPED_ENGINE_GEAR and gear >= 0:
                    if not self.__engineStartAnimTimer.isStarted():
                        states.append((VEHICLE_VIEW_STATE.VEHICLE_ENGINE_STATE, True))
                        self.__engineStartAnimTimer.start()
                elif self.__gear > 0 and not self.__gear:
                    pass
                elif self.__gear >= 0 and gear == _STOPPED_ENGINE_GEAR:
                    pass
                self.__gear = gear
        return states

    def _stopVehMoveAnim(self):
        """
        Stops 'vehicle start' animation by sending the appropriate event.
        """
        if self.__vehMoveAnimTimer.isStarted():
            self.__vehMoveAnimTimer.stop()
            self.notifyStateChanged(VEHICLE_VIEW_STATE.VEHICLE_MOVEMENT_STATE, False)

    def _stopEngineStartAnim(self):
        """
        Stops 'engine start' animation by sending the appropriate event.
        """
        if self.__engineStartAnimTimer.isStarted():
            self.__engineStartAnimTimer.stop()
            self.notifyStateChanged(VEHICLE_VIEW_STATE.VEHICLE_ENGINE_STATE, False)
class _EngineStateHandler(_StateHandler):
    """
    The class provides logic to track engine state changes and vehicle movement changes.
    All logic is based on changes of the current gear.
    """
    __slots__ = ('__weakref__', '__gear', '__vehMoveAnimTimer', '__engineStartAnimTimer')

    def __init__(self, updater, vehicle):
        """
        Constructor. Initializes internal variables based on vehicle state.
        
        :param updater: An instance of _VehicleUpdater class, that creates this handler.
        :param vehicle: Vehicle to be tracked.
        :return:
        """
        super(_EngineStateHandler, self).__init__(updater)
        self.__gear = vehicle.appearance.gear if vehicle is not None else _STOPPED_ENGINE_GEAR
        self.__vehMoveAnimTimer = TimeInterval(_VEHICLE_ANIM_DURATION, self, '_stopVehMoveAnim')
        self.__engineStartAnimTimer = TimeInterval(_VEHICLE_ANIM_DURATION, self, '_stopEngineStartAnim')
        return

    def clear(self):
        """
        Resets internal variables.
        """
        self.__gear = _STOPPED_ENGINE_GEAR
        self._stopVehMoveAnim()
        self._stopEngineStartAnim()

    def _invalidate(self, vehicle):
        """
        The method determines whether one of the following events has occurred and exposes the
        appropriate state change to the updater:
            1. Vehicle starts moving.
            2. Engine is started.
        By these events, appropriate animations are run on UI. To stop animations appropriate state
        change event is sent in _VEHICLE_ANIM_DURATION time. Note that animation duration depends
        on sound effect (track length). But right now there is no way to determine effect duration.
        Therefore for all vehicles the same constant is used.
        
        :param vehicle: A reference to the tracked vehicle(BW entity).
        :return: The state change if any. Otherwise an empty list.
        """
        states = []
        gear = vehicle.appearance.gear
        if self.__gear != gear:
            if not self.__gear and gear > 0:
                if not self.__vehMoveAnimTimer.isStarted():
                    states.append((VEHICLE_VIEW_STATE.VEHICLE_MOVEMENT_STATE, True))
                    self.__vehMoveAnimTimer.start()
            elif self.__gear == _STOPPED_ENGINE_GEAR and gear >= 0:
                if not self.__engineStartAnimTimer.isStarted():
                    states.append((VEHICLE_VIEW_STATE.VEHICLE_ENGINE_STATE, True))
                    self.__engineStartAnimTimer.start()
            elif self.__gear > 0 and not self.__gear:
                pass
            elif self.__gear >= 0 and gear == _STOPPED_ENGINE_GEAR:
                pass
            self.__gear = gear
        return states

    def _stopVehMoveAnim(self):
        """
        Stops 'vehicle start' animation by sending the appropriate event.
        """
        if self.__vehMoveAnimTimer.isStarted():
            self.__vehMoveAnimTimer.stop()
            self.notifyStateChanged(VEHICLE_VIEW_STATE.VEHICLE_MOVEMENT_STATE, False)

    def _stopEngineStartAnim(self):
        """
        Stops 'engine start' animation by sending the appropriate event.
        """
        if self.__engineStartAnimTimer.isStarted():
            self.__engineStartAnimTimer.stop()
            self.notifyStateChanged(VEHICLE_VIEW_STATE.VEHICLE_ENGINE_STATE, False)
Example #7
0
class IndicatorManager(object):
    def __init__(self, config):
        self.__config = config
        self.__panels = []
        self.__isSetHandler = False
        self.__visible = False
        self.__crosshairPosition = [0, 0]
        self.__onShoot = None
        self.__onShot = None
        self.__onShotResult = None
        self.__intervalHandlers = Event()
        self.__eventHandlers = Event()
        self.__keyHandlers = {}
        interval = config['common']['updateInterval']
        self.__timeInterval = TimeInterval(interval, self, 'onWatchStats')
        g_eventBus.addListener(events.AppLifeCycleEvent.INITIALIZED,
                               self.onAppInitialized)
        g_eventBus.addListener(events.AppLifeCycleEvent.DESTROYED,
                               self.onAppDestroyed)
        appLoader = dependency.instance(IAppLoader)
        appLoader.onGUISpaceEntered += self.onGUISpaceEntered
        appLoader.onGUISpaceLeft += self.onGUISpaceLeft
        g_statsCollector.eventHandlers.clear()

    def initPanel(self):
        _logger.info('initPanel')
        self.addHandler()
        g_statsCollector.eventHandlers += self.onEvent
        g_statsCollector.start()
        g_statsCollector.updateArenaInfo()
        clientStatus = g_statsCollector.clientStatus
        self.__panels = []
        self.__keyHandlers = {}
        for paneldef in self.__config.get('panelDefs', []):
            if paneldef['channel'] == 'indicator':
                panel = StatsIndicator(paneldef, clientStatus)
                if 'events' in paneldef:
                    self.__eventHandlers += panel.onEvent
                else:
                    self.__intervalHandlers += panel.update
                if 'toggleKey' in paneldef['style']:
                    keyName = paneldef['style']['toggleKey']
                    keyId = getattr(Keys, keyName)
                    if keyId not in self.__keyHandlers:
                        self.__keyHandlers[keyId] = Event()
                    self.__keyHandlers[keyId] += panel.toggle
            elif paneldef['channel'] == 'status':
                panel = StatsLogger(paneldef, clientStatus)
                self.__intervalHandlers += panel.update
            elif paneldef['channel'] == 'event':
                panel = EventLogger(paneldef, clientStatus)
                self.__eventHandlers += panel.onEvent
            self.__panels.append(panel)
        session = dependency.instance(IBattleSessionProvider)
        ctrl = session.shared.crosshair
        self.changeView(ctrl.getViewID())
        self.updateScreenPosition()
        self.updateCrosshairPosition()

    def finiPanel(self):
        _logger.info('finiPanel')
        self.stopIntervalTimer()
        self.invisiblePanel()
        self.removeHandler()
        for panel in self.__panels:
            if isinstance(panel, EventLogger):
                _logger.info('del EventLogger.onEvent')
                g_statsCollector.eventHandlers -= panel.onEvent
        self.__panels = []
        self.__keyHandlers = {}

    def addHandler(self):
        if self.__isSetHandler:
            return
        self.__isSetHandler = True
        arena = BigWorld.player().arena
        arena.onPeriodChange += self.onArenaPeriodChange
        session = dependency.instance(IBattleSessionProvider)
        ctl = session.shared.vehicleState
        ctl.onVehicleStateUpdated += self.onVehicleStateUpdated
        ctl = session.shared.crosshair
        ctl.onCrosshairViewChanged += self.onCrosshairViewChanged
        ctl.onCrosshairPositionChanged += self.onCrosshairPositionChanged
        self.__crosshairPosition = list(ctl.getScaledPosition())
        g_guiResetters.add(self.onScreenResolutionChanged)
        g_keyEventHandlers.add(self.__handleKeyEvent)

    def removeHandler(self):
        if not self.__isSetHandler:
            return
        self.__isSetHandler = False
        player = BigWorld.player()
        if player:
            arena = player.arena
            if arena:
                arena.onPeriodChange -= self.onArenaPeriodChange
        session = dependency.instance(IBattleSessionProvider)
        ctl = session.shared.vehicleState
        if ctl:
            ctl.onVehicleStateUpdated -= self.onVehicleStateUpdated
        ctl = session.shared.crosshair
        if ctl:
            ctl.onCrosshairViewChanged -= self.onCrosshairViewChanged
            ctl.onCrosshairPositionChanged -= self.onCrosshairPositionChanged
        g_guiResetters.remove(self.onScreenResolutionChanged)
        g_keyEventHandlers.remove(self.__handleKeyEvent)

    def visiblePanel(self):
        if self.__visible:
            return
        self.__visible = True
        _logger.info('panel.start')
        for panel in self.__panels:
            panel.start()

    def invisiblePanel(self):
        if not self.__visible:
            return
        self.__visible = False
        _logger.info('panel.stop')
        for panel in self.__panels:
            panel.stop()

    def startIntervalTimer(self):
        if not self.__timeInterval.isStarted():
            _logger.info('TimeInterval: start')
            self.__timeInterval.start()

    def stopIntervalTimer(self):
        if self.__timeInterval.isStarted():
            _logger.info('TimeInterval: stop')
            self.__timeInterval.stop()

    def changeView(self, viewID):
        _logger.debug('changeView: %s', CROSSHAIR_VIEW_SYMBOL[viewID])
        for panel in self.__panels:
            panel.changeView(viewID)

    def updateScreenPosition(self):
        width, height = GUI.screenResolution()
        _logger.debug('updateScreenPosition: (%d, %d)', width, height)
        for panel in self.__panels:
            panel.updateScreenPosition(width, height)

    def updateCrosshairPosition(self):
        x, y = self.__crosshairPosition
        _logger.debug('updateCrosshairPosition: (%d, %d)', x, y)
        for panel in self.__panels:
            _logger.debug('updateCrosshairPosition: call panel: "%s"',
                          panel.name)
            panel.updateCrosshairPosition(x, y)

    def onAppInitialized(self, event):
        if event.ns != APP_NAME_SPACE.SF_BATTLE:
            return
        _logger.info('AppLifeCycleEvent.INITIALIZED: SF_BATTLE')
        #self.initPanel()

    def onAppDestroyed(self, event):
        if event.ns != APP_NAME_SPACE.SF_BATTLE:
            return
        _logger.info('AppLifeCycleEvent.DESTROYED: SF_BATTLE')
        self.finiPanel()

    def onGUISpaceEntered(self, spaceID):
        if spaceID != GuiGlobalSpaceID.BATTLE:
            return
        _logger.info('onGUISpaceEnterd: %s', GUI_GLOBAL_SPACE_SYMBOL[spaceID])
        self.initPanel()

    def onGUISpaceLeft(self, spaceID):
        if spaceID != GuiGlobalSpaceID.BATTLE:
            return
        _logger.info('onGUISpaceLeft: %s', GUI_GLOBAL_SPACE_SYMBOL[spaceID])
        self.finiPanel()

    def onArenaPeriodChange(self, period, periodEndTime, periodLength,
                            periodAdditionalInfo):
        _logger.info('onArenaPeriodChange: %s', ARENA_PERIOD_SYMBOL[period])
        if period == ARENA_PERIOD.PREBATTLE:
            self.visiblePanel()
        elif period == ARENA_PERIOD.BATTLE:
            self.visiblePanel()
            self.startIntervalTimer()
        elif period == ARENA_PERIOD.AFTERBATTLE:
            self.stopIntervalTimer()
            self.invisiblePanel()

    def onVehicleStateUpdated(self, stateID, value):
        if stateID == VEHICLE_VIEW_STATE.DESTROYED:
            _logger.info('onVehicleStateUpdated: VEHICLE_VIEW_STATE.DESTROYED')
            self.stopIntervalTimer()
            self.invisiblePanel()

    def onScreenResolutionChanged(self):
        width, height = GUI.screenResolution()
        _logger.debug('onScreenResolutionChanged: (%d, %d)', width, height)
        for panel in self.__panels:
            panel.updateScreenPosition(width, height)

    def onCrosshairViewChanged(self, viewID):
        _logger.debug('crosshairViewChanged: %s',
                      CROSSHAIR_VIEW_SYMBOL[viewID])
        self.changeView(viewID)

    def onCrosshairPositionChanged(self, x, y):
        _logger.debug('onCrosshairPositionChanged: (%d, %d)', x, y)
        self.__crosshairPosition = [x, y]
        for panel in self.__panels:
            panel.updateCrosshairPosition(x, y)

    def onWatchStats(self):
        BigWorld.callback(0, partial(self.__intervalHandlers))

    def onEvent(self, reason):
        BigWorld.callback(0, partial(self.__eventHandlers, reason))

    def __handleKeyEvent(self, event):
        if event.isKeyDown() and not event.isRepeatedEvent():
            handlers = self.__keyHandlers.get(event.key, None)
            if handlers is not None:
                handlers()
                return True
        return False
class VehicleStateController(IBattleController):

    def __init__(self):
        super(VehicleStateController, self).__init__()
        self.__eManager = Event.EventManager()
        self.onVehicleStateUpdated = Event.Event(self.__eManager)
        self.onVehicleControlling = Event.Event(self.__eManager)
        self.onPostMortemSwitched = Event.Event(self.__eManager)
        self.onRespawnBaseMoving = Event.Event(self.__eManager)
        self.onEquipmentComponentUpdated = Event.ContextEvent(self.__eManager)
        self.__cachedStateValues = {}
        self.__cachedRepairingCallbackID = None
        self.__waitingTI = TimeInterval(VEHICLE_WAINING_INTERVAL, self, '_waiting')
        self.__vehicleID = 0
        self.__updater = None
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        self.__needInvalidate = False
        return

    def getControllerID(self):
        return BATTLE_CTRL_ID.OBSERVED_VEHICLE_STATE

    def startControl(self, *_):
        pass

    def stopControl(self):
        if self.__waitingTI is not None:
            self.__waitingTI.stop()
            self.__waitingTI = None
        if self.__updater is not None:
            self.__updater.clear()
            self.__updater = None
        self.__vehicleID = 0
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        self.__eManager.clear()
        self.__cachedStateValues.clear()
        if self.__cachedRepairingCallbackID:
            BigWorld.cancelCallback(self.__cachedRepairingCallbackID)
        return

    @property
    def isInPostmortem(self):
        return self.__isInPostmortem

    def setPlayerVehicle(self, vehicleID):
        self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, vehicleID)
        self.__vehicleID = vehicleID
        self.__updater = _VehicleUpdater(self, self.__vehicleID)
        self.__waitingTI.restart()

    def getControllingVehicle(self):
        vehicle = None
        if self.__vehicleID:
            vehicle = BigWorld.entity(self.__vehicleID)
        return vehicle

    def getControllingVehicleID(self):
        return self.__vehicleID

    def notifyStateChanged(self, stateID, value):
        if stateID == VEHICLE_VIEW_STATE.DEVICES:
            self.__cachedStateValues.setdefault(stateID, {})
            deviceName = value[0]
            cachedRepairingDeviceName = first(self.__cachedStateValues.get(VEHICLE_VIEW_STATE.REPAIRING, ()))
            if cachedRepairingDeviceName == deviceName and value[2] == DEVICE_STATE_NORMAL:
                self.__cachedStateValues.pop(VEHICLE_VIEW_STATE.REPAIRING)
            self.__cachedStateValues[stateID][deviceName] = value
        else:
            if stateID == VEHICLE_VIEW_STATE.REPAIRING:
                if self.__cachedRepairingCallbackID:
                    BigWorld.cancelCallback(self.__cachedRepairingCallbackID)
                BigWorld.callback(value[2], partial(self.__cachedRepairingCallback, value))
            self.__cachedStateValues[stateID] = value
        self.onVehicleStateUpdated(stateID, value)

    def __cachedRepairingCallback(self, value):
        self.__cachedRepairingCallbackID = None
        if self.__cachedStateValues.get(VEHICLE_VIEW_STATE.REPAIRING) == value:
            self.__cachedStateValues.pop(VEHICLE_VIEW_STATE.REPAIRING)
        return

    def getStateValue(self, stateID):
        if stateID in self.__cachedStateValues:
            if stateID == VEHICLE_VIEW_STATE.DEVICES:
                value = self.__cachedStateValues[stateID].values()
            else:
                value = self.__cachedStateValues[stateID]
            return value
        else:
            return None

    def refreshVehicleStateValue(self, stateID):
        if stateID in self.__cachedStateValues:
            self.onVehicleStateUpdated(stateID, self.__cachedStateValues[stateID])

    def invalidate(self, state, value, vehicleID=0):
        if vehicleID != 0 and vehicleID != self.__vehicleID:
            return
        else:
            isStateChangeHandled = False
            if self.__isRqToSwitch and self.__waitingTI.isStarted():
                self._waiting()
            if self.__updater is not None:
                isStateChangeHandled = self.__updater.handleStateChange(state, value)
            if not isStateChangeHandled:
                self.notifyStateChanged(state, value)
            return

    def switchToPostmortem(self, noRespawnPossible, respawnAvailable):
        self.__isRqToSwitch = False
        if avatar_getter.getPlayerVehicleID() == self.__vehicleID:
            if self.__updater is not None:
                self.__updater.stop()
                self.__updater.updateOnce()
        self.__isInPostmortem = True
        self.onPostMortemSwitched(noRespawnPossible, respawnAvailable)
        return

    def switchToOther(self, vehicleID):
        if vehicleID is None:
            self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, 0)
            self.__needInvalidate = True
            return
        elif self.__vehicleID == vehicleID and not self.__needInvalidate:
            return
        else:
            self.__needInvalidate = False
            self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, vehicleID)
            self.__waitingTI.stop()
            if self.__updater:
                self.__updater.stop()
            self.__vehicleID = vehicleID
            self.__isRqToSwitch = True
            self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, self.__vehicleID)
            self.__cachedStateValues.clear()
            self.__waitingTI.start()
            return

    def refreshObserverVehicleVisual(self):
        vehicle = self.getControllingVehicle()
        if vehicle is not None:
            self.onVehicleControlling(vehicle)
        return

    def movingToRespawn(self):
        self.__isInPostmortem = False
        self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, 0)
        self.onRespawnBaseMoving()
        self.__cachedStateValues.clear()

    def _waiting(self):
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None and vehicle.isStarted:
            self.__waitingTI.stop()
            self._setup(vehicle)
        return

    def _setup(self, vehicle):
        if self.__updater is not None:
            self.__updater = self.__updater.switch(self.__vehicleID)
        if self.__isRqToSwitch:
            nationID = vehicle.typeDescriptor.type.id[0]
            notifications = avatar_getter.getSoundNotifications()
            if notifications is not None:
                notifications.clear()
            SoundGroups.g_instance.soundModes.setCurrentNation(nations.NAMES[nationID])
        self.onVehicleControlling(vehicle)
        if VEHICLE_VIEW_STATE.DUAL_GUN_STATE_UPDATED in self.__cachedStateValues.keys():
            self.onVehicleStateUpdated(VEHICLE_VIEW_STATE.DUAL_GUN_STATE_UPDATED, self.getStateValue(VEHICLE_VIEW_STATE.DUAL_GUN_STATE_UPDATED))
        if self.__updater is not None:
            self.__updater.start()
        return
class VehicleStateController(IBattleController):
    """
    Class of controller that tracks specified vehicle on arena. At any time there is only one
    instance of this class. When player's own vehicle is destroyed, controller can track other
    vehicles on player's team.
    The main goal of the controller to combine all knowledge about vehicle state changes and to
    notify subscribers about that through appropriate events. The controller declares public
    interface to know about changes. Triggers call these method to notify the controller about
    specific changes that occur outside client. Also there is a list of changes that are triggered
    directly by the controller via the updater. The updater tracks those parameters on which there
    are no outside triggers or when required to control triggering frequency.
    """

    def __init__(self):
        """
        Constructor. Initializes internal variables.
        """
        super(VehicleStateController, self).__init__()
        self.__eManager = Event.EventManager()
        self.onVehicleStateUpdated = Event.Event(self.__eManager)
        self.onVehicleControlling = Event.Event(self.__eManager)
        self.onPostMortemSwitched = Event.Event(self.__eManager)
        self.onRespawnBaseMoving = Event.Event(self.__eManager)
        self.__cachedStateValues = {}
        self.__waitingTI = TimeInterval(VEHICLE_WAINING_INTERVAL, self, '_waiting')
        self.__vehicleID = 0
        self.__updater = None
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        return

    def getControllerID(self):
        return BATTLE_CTRL_ID.OBSERVED_VEHICLE_STATE

    def startControl(self):
        pass

    def stopControl(self):
        """
        Clears internal variables and stops timers.
        :return:
        """
        if self.__waitingTI is not None:
            self.__waitingTI.stop()
            self.__waitingTI = None
        if self.__updater is not None:
            self.__updater.clear()
            self.__updater = None
        self.__vehicleID = 0
        self.__isRqToSwitch = False
        self.__isInPostmortem = False
        self.__eManager.clear()
        self.__cachedStateValues.clear()
        return

    @property
    def isInPostmortem(self):
        return self.__isInPostmortem

    def setPlayerVehicle(self, vehicleID):
        """
        Sets ID of player's own vehicle.
        
        :param vehicleID: ID of vehicle entity
        """
        self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, vehicleID)
        self.__vehicleID = vehicleID
        self.__updater = _VehicleUpdater(self, self.__vehicleID)
        self.__waitingTI.start()

    def getControllingVehicle(self):
        """
        Gets entity of vehicle that is controlling.
        
        :return: entity of vehicle or None
        """
        vehicle = None
        if self.__vehicleID:
            vehicle = BigWorld.entity(self.__vehicleID)
        return vehicle

    def getControllingVehicleID(self):
        return self.__vehicleID

    def notifyStateChanged(self, stateID, value):
        """
        Store the specified state value, and raise the event
        :param stateID: id for state, listed within VEHICLE_VIEW_STATE
        :param value: state value
        """
        if stateID == VEHICLE_VIEW_STATE.DEVICES:
            self.__cachedStateValues.setdefault(stateID, [])
            self.__cachedStateValues[stateID].append(value)
        else:
            self.__cachedStateValues[stateID] = value
        self.onVehicleStateUpdated(stateID, value)

    def getStateValue(self, stateID):
        """
        Returns the last value for the the desired state
        :param stateID: id of state, listed within VEHICLE_VIEW_STATE
        :return: None or state value
        """
        if stateID in self.__cachedStateValues:
            return self.__cachedStateValues[stateID]
        else:
            return None
            return None

    def invalidate(self, state, value, vehicleID = 0):
        """
        Invalidates vehicle sates. The method should be invoked by vehicle state change trigger.
        
        :param state: A state listed within VEHICLE_VIEW_STATE
        :param value: New state value
        :param vehicleID: ID of vehicle for which the given state is changed.
        """
        if vehicleID != 0 and vehicleID != self.__vehicleID:
            return
        else:
            isStateChangeHandled = False
            if self.__isRqToSwitch and self.__waitingTI.isStarted():
                self._waiting()
            if self.__updater is not None:
                isStateChangeHandled = self.__updater.handleStateChange(state, value)
            if not isStateChangeHandled:
                self.notifyStateChanged(state, value)
            return

    def switchToPostmortem(self):
        """
        Switches to postmortem mode.
        """
        self.__isRqToSwitch = False
        if avatar_getter.getPlayerVehicleID() == self.__vehicleID:
            self.__waitingTI.stop()
            if self.__updater is not None:
                self.__updater.stop()
                self.__updater.updateOnce()
        self.__isInPostmortem = True
        self.onPostMortemSwitched()
        return

    def switchToOther(self, vehicleID):
        """
        Switches to another vehicle.
        
        :param vehicleID: ID of a new vehicle to be tracked.
        """
        if self.__vehicleID == vehicleID or vehicleID is None:
            return
        else:
            self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, vehicleID)
            self.__waitingTI.stop()
            if self.__updater:
                self.__updater.stop()
            self.__vehicleID = vehicleID
            self.__isRqToSwitch = True
            self.notifyStateChanged(VEHICLE_VIEW_STATE.PLAYER_INFO, self.__vehicleID)
            self.__cachedStateValues.clear()
            self.__waitingTI.start()
            return

    def movingToRespawn(self):
        """
        Emits the appropriate event and state change event. The method should be called
        when the vehicle is moved to respawn.
        :return:
        """
        self.notifyStateChanged(VEHICLE_VIEW_STATE.SWITCHING, 0)
        self.onRespawnBaseMoving()

    def _waiting(self):
        """
        Callback to be invoked when the waiting timer is elapsed. Starts the updater when the
        avatar has been switched to the required vehicle.
        """
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None:
            self.__waitingTI.stop()
            self._setup(vehicle)
        return

    def _setup(self, vehicle):
        """
        Set up the updater and other properties
        :param vehicle: Vehicle entity.
        """
        if self.__updater is not None:
            self.__updater = self.__updater.switch(self.__vehicleID)
        if self.__isRqToSwitch:
            nationID = vehicle.typeDescriptor.type.id[0]
            notifications = avatar_getter.getSoundNotifications()
            if notifications is not None:
                notifications.clear()
            SoundGroups.g_instance.soundModes.setCurrentNation(nations.NAMES[nationID])
        self.onVehicleControlling(vehicle)
        if self.__updater is not None:
            self.__updater.start()
        return
class _VehicleUpdater(object):
    """
    The class implements logic for tracking changes of vehicle parameters in equal time intervals.
    It encapsulates different algorithms of tracking based on vehicle type. The inner logic is
    based on the custom state handlers (classes derived from _StateHandler).
    """

    def __init__(self, vehCtrl, vehicleID):
        """
        Constructor. Initializes internal variables.
        
        :param vehCtrl: Reference to vehicle state controller owning this updater.
        :param vehicleID: Unique identifier of vehicle to be tracked
        """
        super(_VehicleUpdater, self).__init__()
        self.__isAlive = True
        self.__handlers = None
        self.__ctrl = weakref.proxy(vehCtrl)
        self.__vehicleID = vehicleID
        self.__updateTI = None
        self._setUpHandlers()
        return

    def handleStateChange(self, state, value):
        """
        Represents a hook to catch states changes before the vehicle state controller propagates
        them to subscribers. Redirects the call to each handler to perform required logic.
        
        :param state: A state listed within VEHICLE_VIEW_STATE
        :param value: State value
        :return: True if any handler returns True. Otherwise - False.
        """
        isStateHandled = False
        for handler in self.__handlers:
            isStateHandled |= handler.handleStateChange(state, value)

        return isStateHandled

    def switch(self, vehicleID):
        """
        Switches the updater to another vehicle: reset internal states and handlers.
        
        :param vehicleID: Unique identifier of a new vehicle that will be tracked
        """
        self.clear()
        self.__vehicleID = vehicleID
        self._setUpHandlers()
        return self

    def start(self):
        """
        Sets up and starts the timer to invalidate handlers.
        """
        if self.__updateTI is None:
            self.__updateTI = TimeInterval(VEHICLE_UPDATE_INTERVAL, self, '_update')
        self.__updateTI.start()
        return

    def stop(self):
        """
        Stops and clears running timer (if any).
        """
        if self.__updateTI is not None:
            self.__updateTI.stop()
            self.__updateTI = None
        return

    def clear(self):
        """
        Clears internal variables and stops timer.
        """
        self.stop()
        if self.__handlers is not None:
            for handler in self.__handlers:
                handler.clear()

            self.__handlers = None
        self.__vehicleID = None
        self.__isAlive = True
        return

    def updateOnce(self):
        """
        Invokes this routine to singe update of information about vehicle if it is needed.
        For example, player's vehicle is destroyed, we need to update modules and crew states, set max speed.
        """
        raise self.__updateTI is None or not self.__updateTI.isStarted() or AssertionError('Updater is started')
        self._update()
        return

    def notifyStateChanged(self, state, value):
        """
        The helper method to provide handler ability to notify subscribers about changes of
        vehicle's states through the vehicle state controller.
        
        :param state: A state listed within VEHICLE_VIEW_STATE
        :param value: State value.
        """
        self.__ctrl.notifyStateChanged(state, value)

    def _update(self):
        """
        Callback on timer tick event. Initiates invalidation of all handlers, get all states
        changes and redirects them to the vehicle state controller by emitting appropriate events.
        """
        states = []
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None and vehicle.isStarted:
            if not vehicle.isAlive() and self.__isAlive:
                self.__isAlive = False
                states.append((VEHICLE_VIEW_STATE.DESTROYED, 0))
            for handler in self.__handlers:
                newStates = handler(vehicle)
                states.extend(newStates)

        for item in states:
            self.notifyStateChanged(*item)

        return

    def _setUpHandlers(self):
        """
        Determines a list of handlers based on vehicle type and sets up them.
        """
        vehicle = BigWorld.entity(self.__vehicleID)
        if vehicle is not None:
            isPlayerVehicle = vehicle.isPlayerVehicle
            if isPlayerVehicle:
                if not vehicle.isAlive():
                    self.__handlers = (_SpeedStateHandler(self, True),)
                else:
                    self.__handlers = (_SpeedStateHandler(self, True), _RpmStateHandler(self, vehicle), _EngineStateHandler(self, vehicle))
            else:
                self.__handlers = (_HealthStateHandler(self),
                 _SpeedStateHandler(self, False),
                 _RpmStateHandler(self, vehicle),
                 _EngineStateHandler(self, vehicle))
        return