コード例 #1
0
ファイル: client.go プロジェクト: RookieGameDevs/surviveler
/*
 * Leave sends a LEAVE message to the client associated to given connection
 */
func (reg *ClientRegistry) Leave(reason string, c *network.Conn) {
	clientData := c.GetUserData().(ClientData)

	// send LEAVE to client
	leave := messages.New(messages.LeaveId, messages.Leave{
		Id:     uint32(clientData.Id),
		Reason: reason,
	})
	if err := c.AsyncSendPacket(leave, 5*time.Millisecond); err != nil {
		// either the client received the LEAVE or not, we will close the
		// connection afterwards, so there's nothing more to do in order to
		// gracefully handle this error
		log.WithError(err).WithField("clientID", clientData.Id).Error("LEAVE message couldn't be sent")
	} else {
		log.WithField("clientID", clientData.Id).Info("LEAVE message has been sent")
	}

	// TODO: Remove this: the client should remain alive even when the connection
	// is closed. Example: when the lobby will be implemented

	// closes the connection, registry cleanup will be performed in OnClose
	go func() {
		time.Sleep(100 * time.Millisecond)
		if !c.IsClosed() {
			c.Close()
		}
	}()
}
コード例 #2
0
ファイル: server.go プロジェクト: RookieGameDevs/surviveler
/*
 * OnClose gets called by the server at connection closing, once by
 * connection. This gives us the chance to unregister a new client and perform
 * client cleanup
 */
func (srv *Server) OnClose(c *network.Conn) {
	log.WithField("addr", c.GetRawConn().RemoteAddr()).Debug("Connection closed")

	// unregister the client before anything
	clientData := c.GetUserData().(ClientData)
	srv.clients.unregister(clientData.Id)

	if clientData.Joined {
		// client is still JOINED so that's a disconnection initiated externally
		// send a LEAVE to the rest of the world
		msg := messages.New(messages.LeaveId,
			messages.Leave{
				Id:     uint32(clientData.Id),
				Reason: "client disconnection",
			})
		srv.Broadcast(msg)
	} else {
		// the client was not marked as JOINED, so nobody knows about him
		// and we have nothing more to do
	}

	if srv.playerLeftCb != nil {
		// raise 'player left' external callback
		srv.playerLeftCb(clientData.Id)
	}
}
コード例 #3
0
ファイル: server.go プロジェクト: RookieGameDevs/surviveler
/*
 * OnIncomingMsg gets called by the server each time a message has been read
 * from the connection
 */
func (srv *Server) OnIncomingPacket(c *network.Conn, packet network.Packet) bool {
	clientData := c.GetUserData().(ClientData)
	raw := packet.(*messages.Message)

	log.WithFields(
		log.Fields{
			"clientData": clientData,
			"addr":       c.GetRawConn().RemoteAddr(),
			"type":       raw.Type.String(),
		}).Debug("Incoming message")

	// decode the raw message
	msg := srv.factory.Decode(raw)

	// get handler
	handler, ok := srv.msgHandlers[raw.Type]
	if ok {
		if err := handler(c, msg); err != nil {
			log.WithError(err).Error("Error handling message")
			return false
		}
	} else {

		switch raw.Type {

		case messages.PingId:

			// immediately reply pong
			ping := msg.(messages.Ping)
			pong := messages.New(messages.PongId,
				messages.Pong{
					Id:     ping.Id,
					Tstamp: time.Now().UnixNano() / int64(time.Millisecond),
				})
			if err := c.AsyncSendPacket(pong, time.Second); err != nil {
				log.WithError(err).Error("Error handling message")
				return false
			}

		case messages.JoinId:

			join := msg.(messages.Join)
			// JOIN is handled by the handshaker
			if srv.handshaker.Join(join, c) {
				// new client has been accepted
				if srv.playerJoinedCb != nil {
					// raise 'player joined' external callback
					srv.playerJoinedCb(clientData.Id, join.Type)
				}
			}
		}
	}

	return true
}
コード例 #4
0
ファイル: client.go プロジェクト: RookieGameDevs/surviveler
func (reg *ClientRegistry) Join(join messages.Join, c *network.Conn) bool {
	clientData := c.GetUserData().(ClientData)

	log.WithFields(log.Fields{"name": join.Name, "clientData": clientData}).Info("Received JOIN from client")

	// client already JOINED?
	if clientData.Joined {
		reg.Leave("Joined already received", c)
		return false
	}

	// name length condition
	if len(join.Name) < 3 {
		reg.Leave("Name is too short", c)
		return false
	}

	// name already taken?
	nameTaken := false
	playerNames := make(map[uint32]string)

	// compute the list of joined players, populating the STAY message and
	// checking if name is taken as well
	reg.ForEach(func(cd ClientData) bool {
		nameTaken = cd.Name == join.Name
		playerNames[cd.Id] = cd.Name
		// stop iteration if name is taken
		return !nameTaken
	})

	if nameTaken {
		reg.Leave("Name is already taken", c)
		return false
	}

	// create and send STAY to the new client
	stay := messages.Stay{Id: clientData.Id, Players: playerNames}
	err := c.AsyncSendPacket(messages.New(messages.StayId, stay), time.Second)
	if err != nil {
		// handle error in case we couldn't send the STAY message
		log.WithError(err).Error("Couldn't send STAY message to the new client")
		reg.Leave("Couldn't finish handshaking", c)
		return false
	}

	// fill a JOINED message
	joined := &messages.Joined{
		Id:   clientData.Id,
		Name: join.Name,
		Type: join.Type,
	}

	log.WithField("joined", joined).Info("Tell to the world this client has joined")
	reg.Broadcast(messages.New(messages.JoinedId, joined))

	// at this point we consider the client as accepted
	clientData.Joined = true
	clientData.Name = join.Name
	c.SetUserData(clientData)
	return true
}
コード例 #5
0
ファイル: loop.go プロジェクト: RookieGameDevs/surviveler
/*
 * loop is the main game loop, it fetches messages from a channel, processes
 * them immediately. After performing some initialization, it waits forever,
 * waiting for a wake-up call coming from any one of those events:
 * - external loop close request -> exits immediately
 * - arrival of a message -> process it
 * - logic tick -> perform logic update
 * - gamestate tick -> pack and broadcast the current game state
 * - telnet request -> perform a game state related telnet request
 */
func (g *Game) loop() error {
	// will tick when it's time to send the gamestate to the clients
	sendTickChan := time.NewTicker(
		time.Millisecond * time.Duration(g.cfg.SendTickPeriod)).C

	// will tick when it's time to update the game
	tickChan := time.NewTicker(
		time.Millisecond * time.Duration(g.cfg.LogicTickPeriod)).C

	// will tick when a minute in game time elapses
	timeChan := time.NewTicker(
		time.Minute * 1 / time.Duration(g.cfg.TimeFactor)).C

	// event listeners
	g.eventManager.Subscribe(events.PlayerJoinId, g.state.onPlayerJoin)
	g.eventManager.Subscribe(events.PlayerLeaveId, g.state.onPlayerLeave)
	g.eventManager.Subscribe(events.PlayerMoveId, g.state.onPlayerMove)
	g.eventManager.Subscribe(events.PlayerBuildId, g.state.onPlayerBuild)
	g.eventManager.Subscribe(events.PlayerRepairId, g.state.onPlayerRepair)
	g.eventManager.Subscribe(events.PlayerAttackId, g.state.onPlayerAttack)
	g.eventManager.Subscribe(events.PlayerOperateId, g.state.onPlayerOperate)
	g.eventManager.Subscribe(events.PlayerDeathId, g.state.onPlayerDeath)
	g.eventManager.Subscribe(events.ZombieDeathId, g.state.onZombieDeath)
	g.eventManager.Subscribe(events.ZombieDeathId, g.ai.OnZombieDeath)
	g.eventManager.Subscribe(events.BuildingDestroyId, g.state.onBuildingDestroy)

	var lastTime, curTime time.Time
	lastTime = time.Now()
	log.Info("Starting game loop")
	g.wg.Add(1)

	go func() {
		defer func() {
			g.wg.Done()
			log.Info("Stopping game loop")
		}()

		for {
			select {

			case <-g.quitChan:
				return

			case <-sendTickChan:
				// pack the gamestate into a message
				if gsMsg := g.state.pack(); gsMsg != nil {
					// wrap the gameStateMsg into a generic Message
					if msg := messages.New(messages.GameStateId, *gsMsg); msg != nil {
						g.server.Broadcast(msg)
					}
				}

			case <-tickChan:
				// poll and process accumulated events
				g.eventManager.Process()

				// compute delta time
				curTime = time.Now()
				dt := curTime.Sub(lastTime)

				// update AI
				g.ai.Update(curTime)

				// update entities
				for _, ent := range g.state.entities {
					ent.Update(dt)
				}

				lastTime = curTime

			case <-timeChan:
				// increment game time by 1 minute
				g.state.gameTime++

				// clamp the game time to 24h
				if g.state.gameTime >= 1440 {
					g.state.gameTime -= 1440
				}

			case tnr := <-g.telnetReq:
				// received a telnet request
				g.telnetDone <- g.telnetHandler(tnr)

			default:
				// let the rest of the world spin
				time.Sleep(1 * time.Millisecond)
			}
		}
	}()
	return nil
}