Esempio n. 1
0
    def handle(self):
        data = self.request[0]
        socket = self.request[1]
        recv = udp_node_msgs_pb2.ClientToServer()

        try:
            recv.ParseFromString(data[:-4])
        except:
            try:
                #If no sensors connected, first byte must be skipped
                recv.ParseFromString(data[1:-4])
            except:
                return

        client_address = self.client_address
        player_id = recv.player_id
        state = recv.state

        #Add last updates for player if missing
        if not player_id in last_updates.keys():
            last_updates[player_id] = 0
        if not player_id in last_online_updates.keys():
            last_online_updates[player_id] = 0
        if not player_id in last_pp_updates.keys():
            last_pp_updates[player_id] = 0
        if not player_id in last_bot_updates.keys():
            last_bot_updates[player_id] = 0

        t = int(zwift_offline.get_utc_time())

        #Update player online state
        if state.roadTime and t >= last_updates[player_id] + online_update_freq:
            last_updates[player_id] = t
            if not player_id in online.keys():
                discord.send_message('%s riders online' % (len(online) + 1))
            online[player_id] = state

        #Add handling of ghosts for player if it's missing
        if not player_id in global_ghosts.keys():
            global_ghosts[player_id] = GhostsVariables()

        ghosts = global_ghosts[player_id]
        ghosts.last_package_time = t

        if recv.seqno == 1:
            ghosts.rec = None
            organize_ghosts(player_id)

        #Changed course
        if get_course(state) and ghosts.course != get_course(state):
            ghosts.rec = None
            ghosts.course = get_course(state)

        if ghosts.rec == None:
            ghosts.rec = udp_node_msgs_pb2.Ghost()
            ghosts.play = udp_node_msgs_pb2.Ghosts()
            ghosts.last_rt = 0
            ghosts.play_count = 0
            ghosts.loaded = False
            ghosts.started = False
            ghosts.rec.player_id = player_id

        if player_id in ghosts_enabled and ghosts_enabled[player_id]:
            #Load ghosts for current course
            if not ghosts.loaded and get_course(state):
                ghosts.loaded = True
                load_ghosts(player_id, state, ghosts)
            #Save player state as ghost if moving
            if state.roadTime and ghosts.last_rt and state.roadTime != ghosts.last_rt:
                if t >= ghosts.last_rec + ghost_update_freq:
                    s = ghosts.rec.states.add()
                    s.CopyFrom(state)
                    ghosts.last_rec = t
                #Start loaded ghosts
                if not ghosts.started and ghosts.play.ghosts and road_id(
                        state) == ghosts.start_road:
                    if is_forward(state):
                        if state.roadTime > ghosts.start_rt and abs(
                                state.roadTime - ghosts.start_rt) < 500000:
                            ghosts.started = True
                    else:
                        if state.roadTime < ghosts.start_rt and abs(
                                state.roadTime - ghosts.start_rt) < 500000:
                            ghosts.started = True
            #Uncomment to print player state when stopped (to find new start lines)
            #else: print('course', get_course(state), 'road', road_id(state), 'isForward', is_forward(state), 'roadTime', state.roadTime)
            ghosts.last_rt = state.roadTime

        #Set state of player being watched
        watching_state = None
        if state.watchingRiderId == player_id:
            watching_state = state
        elif state.watchingRiderId in online.keys():
            watching_state = online[state.watchingRiderId]
        elif state.watchingRiderId in global_pace_partners.keys():
            pp = global_pace_partners[state.watchingRiderId]
            watching_state = pp.route.states[pp.position]
        elif state.watchingRiderId in global_bots.keys():
            bot = global_bots[state.watchingRiderId]
            watching_state = bot.route.states[bot.position]
        elif state.watchingRiderId > 10000000:
            ghost = ghosts.play.ghosts[math.floor(state.watchingRiderId /
                                                  10000000) - 1]
            if len(ghost.states) > ghosts.play_count:
                watching_state = ghost.states[ghosts.play_count]

        #Check if online players, pace partners, bots and ghosts are nearby
        nearby = list()
        if t >= last_online_updates[player_id] + online_update_freq:
            last_online_updates[player_id] = t
            for p_id in online.keys():
                player = online[p_id]
                if player.id != player_id and zwift_offline.is_nearby(
                        watching_state, player):
                    nearby.append(p_id)
        if t >= last_pp_updates[player_id] + pacer_update_freq:
            last_pp_updates[player_id] = t
            for p_id in global_pace_partners.keys():
                pace_partner_variables = global_pace_partners[p_id]
                pace_partner = pace_partner_variables.route.states[
                    pace_partner_variables.position]
                if zwift_offline.is_nearby(watching_state, pace_partner):
                    nearby.append(p_id)
        if t >= last_bot_updates[player_id] + bot_update_freq:
            last_bot_updates[player_id] = t
            for p_id in global_bots.keys():
                bot_variables = global_bots[p_id]
                bot = bot_variables.route.states[bot_variables.position]
                if zwift_offline.is_nearby(watching_state, bot):
                    nearby.append(p_id)
        if ghosts.started and t >= ghosts.last_play + ghost_update_freq:
            ghosts.last_play = t
            ghost_id = 1
            for g in ghosts.play.ghosts:
                if len(g.states
                       ) > ghosts.play_count and zwift_offline.is_nearby(
                           watching_state, g.states[ghosts.play_count]):
                    nearby.append(player_id + ghost_id * 10000000)
                ghost_id += 1
            ghosts.play_count += 1

        #Send nearby riders states or empty message
        message = get_empty_message(player_id)
        if nearby:
            message.num_msgs = math.ceil(len(nearby) / 10)
            for p_id in nearby:
                player = None
                if p_id in online.keys():
                    player = online[p_id]
                elif p_id in global_pace_partners.keys():
                    pace_partner_variables = global_pace_partners[p_id]
                    player = pace_partner_variables.route.states[
                        pace_partner_variables.position]
                elif p_id in global_bots.keys():
                    bot_variables = global_bots[p_id]
                    player = bot_variables.route.states[bot_variables.position]
                elif p_id > 10000000:
                    player = ghosts.play.ghosts[math.floor(p_id / 10000000) -
                                                1].states[ghosts.play_count -
                                                          1]
                    player.id = p_id
                    player.worldTime = zwift_offline.world_time()
                if player != None:
                    if len(message.states) > 9:
                        message.world_time = zwift_offline.world_time()
                        socket.sendto(message.SerializeToString(),
                                      client_address)
                        message.msgnum += 1
                        del message.states[:]
                    s = message.states.add()
                    s.CopyFrom(player)
        else:
            message.num_msgs = 1
        message.world_time = zwift_offline.world_time()
        socket.sendto(message.SerializeToString(), client_address)
Esempio n. 2
0
    def handle(self):
        global seqno
        global rec
        global play
        global last_rec
        global last_play
        global play_count
        global last_rt
        global ghosts_enabled
        global ghosts_loaded
        global ghosts_started

        data = self.request[0]
        socket = self.request[1]
        recv = udp_node_msgs_pb2.ClientToServer()
        try:
            recv.ParseFromString(data[:-4])
        except:
            return

        if recv.seqno == 1:
            del rec.states[:]
            del play.ghosts[:]
            seqno = 1
            last_rt = 0
            play_count = 0
            ghosts_enabled = os.path.exists(ENABLEGHOSTS_FILE)
            ghosts_loaded = False
            ghosts_started = False
            rec.player_id = recv.player_id
            organizeGhosts(recv.player_id)

        t = int(time.time())

        if ghosts_enabled:
            if not ghosts_loaded and course(recv.state):
                ghosts_loaded = True
                loadGhosts(recv.player_id, recv.state)
            if recv.state.roadTime and last_rt and recv.state.roadTime != last_rt:
                if t >= last_rec + update_freq:
                    state = rec.states.add()
                    state.CopyFrom(recv.state)
                    last_rec = t
                if not ghosts_started and play.ghosts and roadID(recv.state) == start_road:
                    if isForward(recv.state):
                        if recv.state.roadTime >= start_rt >= last_rt:
                            ghosts_started = True
                    else:
                        if recv.state.roadTime <= start_rt <= last_rt:
                            ghosts_started = True
            last_rt = recv.state.roadTime

        if ghosts_started and t >= last_play + update_freq:
            message = udp_node_msgs_pb2.ServerToClient()
            message.f1 = 1
            message.player_id = recv.player_id
            message.f5 = 1
            message.f11 = 1
            msgnum = 1
            active_ghosts = 0
            for g in play.ghosts:
                if len(g.states) > play_count: active_ghosts += 1
            if active_ghosts:
                message.num_msgs = active_ghosts // 10
                if active_ghosts % 10: message.num_msgs += 1
                ghost_id = 1
                for g in play.ghosts:
                    if len(g.states) > play_count:
                        if len(message.states) < 10:
                            state = message.states.add()
                            state.CopyFrom(g.states[play_count])
                            state.id = ghost_id
                            state.worldTime = zwift_offline.world_time()
                        else:
                            message.world_time = zwift_offline.world_time()
                            message.seqno = seqno
                            message.msgnum = msgnum
                            socket.sendto(message.SerializeToString(), self.client_address)
                            seqno += 1
                            msgnum += 1
                            del message.states[:]
                            state = message.states.add()
                            state.CopyFrom(g.states[play_count])
                            state.id = ghost_id
                            state.worldTime = zwift_offline.world_time()
                    ghost_id += 1
            else: message.num_msgs = 1
            message.world_time = zwift_offline.world_time()
            message.seqno = seqno
            message.msgnum = msgnum
            socket.sendto(message.SerializeToString(), self.client_address)
            seqno += 1
            play_count += 1
            last_play = t
        else:
            message = udp_node_msgs_pb2.ServerToClient()
            message.f1 = 1
            message.player_id = recv.player_id
            message.world_time = zwift_offline.world_time()
            message.seqno = seqno
            message.f5 = 1
            message.f11 = 1
            message.num_msgs = 1
            message.msgnum = 1
            socket.sendto(message.SerializeToString(), self.client_address)
            seqno += 1
Esempio n. 3
0
    def handle(self):
        data = self.request[0]
        socket = self.request[1]
        recv = udp_node_msgs_pb2.ClientToServer()

        try:
            recv.ParseFromString(data[:-4])
        except:
            try:
                recv.ParseFromString(data[3:-4])
            except:
                return

        client_address = self.client_address
        player_id = recv.player_id
        state = recv.state

        nearby_state = state
        if state.watchingRiderId in online.keys():
            nearby_state = online[state.watchingRiderId]
        elif state.watchingRiderId in global_pace_partners.keys():
            pp = global_pace_partners[state.watchingRiderId]
            nearby_state = pp.route.states[pp.position]

        #Add handling of ghosts for player if it's missing
        if not player_id in global_ghosts.keys():
            global_ghosts[player_id] = GhostsVariables()

        ghosts = global_ghosts[player_id]

        #Add pace partner last update for player if it's missing
        if not player_id in last_pp_updates.keys():
            last_pp_updates[player_id] = 0

        last_pp_update = last_pp_updates[player_id]

        #Add bot last update for player if it's missing
        if not player_id in last_bot_updates.keys():
            last_bot_updates[player_id] = 0

        last_bot_update = last_bot_updates[player_id]

        if recv.seqno == 1 or ghosts.rec == None:
            ghosts.rec = udp_node_msgs_pb2.Ghost()
            ghosts.play = udp_node_msgs_pb2.Ghosts()
            ghosts.last_rt = 0
            ghosts.play_count = 0
            ghosts.loaded = False
            ghosts.started = False
            ghosts.rec.player_id = player_id
            organize_ghosts(player_id)

        t = int(zwift_offline.get_utc_time())
        ghosts.last_package_time = t

        if player_id in ghosts_enabled and ghosts_enabled[player_id]:
            if not ghosts.loaded and get_course(state):
                ghosts.loaded = True
                load_ghosts(player_id, state, ghosts)
            if state.roadTime and ghosts.last_rt and state.roadTime != ghosts.last_rt:
                if t >= ghosts.last_rec + ghost_update_freq:
                    s = ghosts.rec.states.add()
                    s.CopyFrom(state)
                    ghosts.last_rec = t
                if not ghosts.started and ghosts.play.ghosts and road_id(
                        state) == ghosts.start_road:
                    if is_forward(state):
                        if state.roadTime >= ghosts.start_rt >= ghosts.last_rt:
                            ghosts.started = True
                    else:
                        if state.roadTime <= ghosts.start_rt <= ghosts.last_rt:
                            ghosts.started = True


#            else: print('course', get_course(state), 'road', road_id(state), 'isForward', is_forward(state), 'roadTime', state.roadTime)
            ghosts.last_rt = state.roadTime

        keys = online.keys()
        remove_players = list()
        for p_id in keys:
            if zwift_offline.world_time() > online[p_id].worldTime + 10000:
                remove_players.insert(0, p_id)
        for p_id in remove_players:
            online.pop(p_id)
        if state.roadTime:
            online[player_id] = state

        #Remove ghosts entries for inactive players (disconnected?)
        keys = global_ghosts.keys()
        remove_players = list()
        for p_id in keys:
            if global_ghosts[p_id].last_package_time < t - 10:
                remove_players.insert(0, p_id)
        for p_id in remove_players:
            global_ghosts.pop(p_id)

        if ghosts.started and t >= ghosts.last_play + ghost_update_freq:
            message = get_empty_message(player_id)
            active_ghosts = 0
            for g in ghosts.play.ghosts:
                if len(g.states) > ghosts.play_count: active_ghosts += 1
            if active_ghosts:
                message.num_msgs = active_ghosts // 10
                if active_ghosts % 10: message.num_msgs += 1
                ghost_id = 1
                for g in ghosts.play.ghosts:
                    if len(g.states) > ghosts.play_count:
                        if len(message.states) < 10:
                            state = message.states.add()
                            state.CopyFrom(g.states[ghosts.play_count])
                            state.id = player_id + ghost_id * 10000000
                            state.worldTime = zwift_offline.world_time()
                        else:
                            message.world_time = zwift_offline.world_time()
                            socket.sendto(message.SerializeToString(),
                                          client_address)
                            message.msgnum += 1
                            del message.states[:]
                            state = message.states.add()
                            state.CopyFrom(g.states[ghosts.play_count])
                            state.id = player_id + ghost_id * 10000000
                            state.worldTime = zwift_offline.world_time()
                    ghost_id += 1
            else:
                message.num_msgs = 1
            message.world_time = zwift_offline.world_time()
            socket.sendto(message.SerializeToString(), client_address)
            ghosts.play_count += 1
            ghosts.last_play = t
        message = get_empty_message(player_id)
        nearby = list()
        for p_id in online.keys():
            player = online[p_id]
            if player.id != player_id:
                #Check if players are close in world
                if zwift_offline.is_nearby(nearby_state, player):
                    nearby.append(p_id)
        if t >= last_pp_update + pacer_update_freq:
            last_pp_updates[player_id] = t
            for p_id in global_pace_partners.keys():
                pace_partner_variables = global_pace_partners[p_id]
                pace_partner = pace_partner_variables.route.states[
                    pace_partner_variables.position]
                #Check if pacepartner is close to player in world
                if zwift_offline.is_nearby(nearby_state, pace_partner):
                    nearby.append(p_id)
        if t >= last_bot_update + bot_update_freq:
            last_bot_updates[player_id] = t
            for p_id in global_bots.keys():
                bot_variables = global_bots[p_id]
                bot = bot_variables.route.states[bot_variables.position]
                #Check if bot is close to player in world
                if zwift_offline.is_nearby(nearby_state, bot):
                    nearby.append(p_id)
        players = len(nearby)
        message.num_msgs = players // 10
        if players % 10: message.num_msgs += 1
        for p_id in nearby:
            player = None
            if p_id in online.keys():
                player = online[p_id]
            elif p_id in global_pace_partners.keys():
                pace_partner_variables = global_pace_partners[p_id]
                player = pace_partner_variables.route.states[
                    pace_partner_variables.position]
            elif p_id in global_bots.keys():
                bot_variables = global_bots[p_id]
                player = bot_variables.route.states[bot_variables.position]
            if player != None:
                if len(message.states) < 10:
                    state = message.states.add()
                    state.CopyFrom(player)
                else:
                    message.world_time = zwift_offline.world_time()
                    socket.sendto(message.SerializeToString(), client_address)
                    message.msgnum += 1
                    del message.states[:]
                    state = message.states.add()
                    state.CopyFrom(player)
        message.world_time = zwift_offline.world_time()
        socket.sendto(message.SerializeToString(), client_address)
Esempio n. 4
0
    def handle(self):
        self.data = self.request.recv(1024)
        if len(self.data) > 3 and self.data[3] != 0:
            print("TCPHandler hello(0) expected, got %s" % self.data[3])
            return
        #print("TCPHandler hello: %s" % self.data.hex())
        hello = udp_node_msgs_pb2.ClientToServer()
        try:
            hello.ParseFromString(
                self.data[4:-4]
            )  #2 bytes: payload length, 1 byte: =0x1 (TcpClient::sendClientToServer) 1 byte: type; payload; 4 bytes: hash
            #type: TcpClient::sayHello(=0x0), TcpClient::sendSubscribeToSegment(=0x1), TcpClient::processSegmentUnsubscription(=0x1)
        except Exception as exc:
            print('TCPHandler ParseFromString exception: %s' % repr(exc))
            return
        # send packet containing UDP server (127.0.0.1)
        # (very little investigation done into this packet while creating
        #  protobuf structures hence the excessive "details" usage)
        msg = udp_node_msgs_pb2.ServerToClient()
        msg.player_id = hello.player_id
        msg.world_time = 0
        if os.path.exists(SERVER_IP_FILE):
            with open(SERVER_IP_FILE, 'r') as f:
                udp_node_ip = f.read().rstrip('\r\n')
        else:
            udp_node_ip = "127.0.0.1"
        details1 = msg.udp_config.relay_addresses.add()
        details1.lb_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
        details1.lb_course = 6  # watopia crowd
        details1.ip = udp_node_ip
        details1.port = 3022
        details2 = msg.udp_config.relay_addresses.add()
        details2.lb_realm = 0  #generic load balancing realm
        details2.lb_course = 0  #generic load balancing course
        details2.ip = udp_node_ip
        details2.port = 3022
        msg.udp_config.uc_f2 = 10
        msg.udp_config.uc_f3 = 30
        msg.udp_config.uc_f4 = 3
        wdetails1 = msg.udp_config_vod_1.relay_addresses_vod.add()
        wdetails1.lb_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
        wdetails1.lb_course = 6  # watopia crowd
        details3 = wdetails1.relay_addresses.add()
        details3.CopyFrom(details1)
        wdetails2 = msg.udp_config_vod_1.relay_addresses_vod.add()
        wdetails2.lb_realm = 0  #generic load balancing realm
        wdetails2.lb_course = 0  #generic load balancing course
        details4 = wdetails2.relay_addresses.add()
        details4.CopyFrom(details2)
        msg.udp_config_vod_1.port = 3022
        payload = msg.SerializeToString()
        # Send size of payload as 2 bytes
        self.request.sendall(struct.pack('!h', len(payload)))
        self.request.sendall(payload)

        player_id = hello.player_id
        #print("TCPHandler for %d" % player_id)
        msg = udp_node_msgs_pb2.ServerToClient()
        msg.player_id = player_id
        msg.world_time = 0
        msg.stc_f11 = True
        payload = msg.SerializeToString()

        last_alive_check = int(zo.get_utc_time())
        self.request.settimeout(1)  #make recv non-blocking
        while True:
            self.data = b''
            try:
                self.data = self.request.recv(1024)
                #print(self.data.hex())
                i = 0
                while i < len(self.data):
                    size = int.from_bytes(self.data[i:i + 2], "big")
                    packet = self.data[i:i + size + 2]
                    #print(packet.hex())
                    if len(packet) == size + 2 and packet[3] == 1:
                        subscr = udp_node_msgs_pb2.ClientToServer()
                        try:
                            subscr.ParseFromString(packet[4:-4])
                            #print(subscr)
                        except Exception as exc:
                            print('TCPHandler ParseFromString exception: %s' %
                                  repr(exc))
                        if subscr.subsSegments:
                            msg1 = udp_node_msgs_pb2.ServerToClient()
                            msg1.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
                            msg1.player_id = subscr.player_id
                            msg1.world_time = zo.world_time()
                            msg1.ackSubsSegm.extend(subscr.subsSegments)
                            payload1 = msg1.SerializeToString()
                            self.request.sendall(
                                struct.pack('!h', len(payload1)))
                            self.request.sendall(payload1)
                            #print('TCPHandler subscr: %s' % msg1.ackSubsSegm)
                    i += size + 2
            except Exception as exc:
                #print('TCPHandler exception: %s' % repr(exc)) #timeout is ok here
                pass

            #Check every 5 seconds for new updates
            #tcpthreadevent.wait(timeout=5) # no more, we will use the request timeout now
            try:
                t = int(zo.get_utc_time())

                #if ZC need to be registered
                if player_id in zo.zc_connect_queue:  # and player_id in online:
                    zc_params = udp_node_msgs_pb2.ServerToClient()
                    zc_params.player_id = player_id
                    zc_params.world_time = 0
                    zc_params.zc_local_ip = zo.zc_connect_queue[player_id][0]
                    zc_params.zc_local_port = zo.zc_connect_queue[player_id][
                        1]  #21587
                    zc_params.zc_protocol = udp_node_msgs_pb2.IPProtocol.TCP  #=2
                    zc_params_payload = zc_params.SerializeToString()
                    last_alive_check = t
                    self.request.sendall(
                        struct.pack('!h', len(zc_params_payload)))
                    self.request.sendall(zc_params_payload)
                    #print("TCPHandler register_zc %d %s" % (player_id, zc_params_payload.hex()))
                    zo.zc_connect_queue.pop(player_id)

                message = udp_node_msgs_pb2.ServerToClient()
                message.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
                message.player_id = player_id
                message.world_time = zo.world_time()

                #PlayerUpdate
                if player_id in player_update_queue and len(
                        player_update_queue[player_id]
                ) > 0 and player_id in online:
                    added_player_updates = list()
                    for player_update_proto in player_update_queue[player_id]:
                        player_update = message.updates.add()
                        player_update.ParseFromString(player_update_proto)

                        #Send if 10 updates has already been added and start a new message
                        if len(message.updates) > 9:
                            message_payload = message.SerializeToString()
                            self.request.sendall(
                                struct.pack('!h', len(message_payload)))
                            self.request.sendall(message_payload)

                            message = udp_node_msgs_pb2.ServerToClient()
                            message.server_realm = udp_node_msgs_pb2.ZofflineConstants.RealmID
                            message.player_id = player_id
                            message.world_time = zo.world_time()

                        added_player_updates.append(player_update_proto)
                    for player_update_proto in added_player_updates:
                        player_update_queue[player_id].remove(
                            player_update_proto)

                #Check if any updates are added and should be sent to client, otherwise just keep alive every 25 seconds
                if len(message.updates) > 0:
                    last_alive_check = t
                    message_payload = message.SerializeToString()
                    self.request.sendall(
                        struct.pack('!h', len(message_payload)))
                    self.request.sendall(message_payload)
                elif last_alive_check < t - 25:
                    last_alive_check = t
                    self.request.sendall(struct.pack('!h', len(payload)))
                    self.request.sendall(payload)
            except Exception as exc:
                print('TCPHandler loop exception: %s' % repr(exc))
                break
Esempio n. 5
0
    def handle(self):
        global rec
        global play
        global last_rec
        global last_play
        global play_count
        global last_rt
        global last_recv
        global ghosts
        data = self.request[0]
        socket = self.request[1]
        recv = udp_node_msgs_pb2.ClientToServer()
        try:
            recv.ParseFromString(data[:-4])
        except:
            return

        if enable_ghosts:
            t = int(time.time())
            if t > last_recv + timeout:
                del rec.states[:]
                del play.ghosts[:]
                last_rt = 0
                play_count = 0
                ghosts = False
            last_recv = t
            if recv.state.roadTime:
                if not last_rt and not play.ghosts:
                    loadGhosts(recv.player_id, recv.state)
                    rec.player_id = recv.player_id
                if last_rt and recv.state.roadTime != last_rt:
                    if t >= last_rec + update_freq:
                        state = rec.states.add()
                        state.CopyFrom(recv.state)
                        last_rec = t
                    if not ghosts and play.ghosts and roadID(recv.state) == start_road:
                        if isForward(recv.state):
                            if recv.state.roadTime >= start_rt >= last_rt:
                                ghosts = True
                        else:
                            if recv.state.roadTime <= start_rt <= last_rt:
                                ghosts = True
            last_rt = recv.state.roadTime

        message = udp_node_msgs_pb2.ServerToClient()
        message.f1 = 1
        message.player_id = recv.player_id
        message.world_time = zwift_offline.world_time()
        message.seqno = 1
        message.f5 = 1

        if ghosts and t >= last_play + update_freq:
            ghost_id = 1
            for g in play.ghosts:
                if len(g.states) > play_count:
                    state = message.states.add()
                    state.CopyFrom(g.states[play_count])
                    state.id = ghost_id
                    state.worldTime = zwift_offline.world_time()
                ghost_id += 1
            last_play = t
            play_count += 1

        message.f11 = 1
        message.num_msgs = 1
        message.msgnum = 1
        socket.sendto(message.SerializeToString(), self.client_address)