func startExcessMission(mission *entities.Mission, homePlanet *entities.Planet, ships int32) {
	newTargetKey := fmt.Sprintf("planet.%s", mission.Source.Name)
	newTargetEntity, err := entities.Get(newTargetKey)
	if err != nil {
		log.Print("Error in newTarget planet fetch: ", err.Error())
		return
	}

	playerEntity, err := entities.Get(fmt.Sprintf("player.%s", mission.Player))
	player := playerEntity.(*entities.Player)

	excessMission := player.StartMission(homePlanet, newTargetEntity.(*entities.Planet), []*vec2d.Vector{}, 100, "Attack")
	excessMission.ShipCount = ships
	go StartMissionary(excessMission)
	entities.Save(excessMission)
	clients.Broadcast(excessMission)
}
Example #2
0
// Returns a slice of friend's suns
func fetchFriendsSuns(twitterName string, api *anaconda.TwitterApi) (suns []*entities.Sun) {
	friendsNames, twitterErr := fetchTwitterFriends(twitterName, api)
	if twitterErr != nil {
		return
	}

	for _, name := range friendsNames {
		playerEntity, err := entities.Get(fmt.Sprintf("player.%s", name))
		if playerEntity == nil || err != nil {
			continue
		}

		player := playerEntity.(*entities.Player)
		if err != nil {
			continue
		}

		if friendSun, sunErr := entities.Get(player.Sun()); sunErr == nil {
			suns = append(suns, friendSun.(*entities.Sun))
		}
	}
	return
}
Example #3
0
// Authenticate is a function called for every client's new session.
// It manages several important tasks at the start of the session.
// 1.Ask the user for Username and twitter ID.
// 2.Search the DB to find the player if it's not a new one.
// 3.If the player is new there is a subsequence initiated:
// 3.1.Create a new sun with GenerateSun
// 3.2.Choose home planet from the newly created solar sysitem.
// 3.3.Create a reccord of the new player and start comunication.
func authenticate(ws *websocket.Conn) (*entities.Player, *anaconda.TwitterApi, error) {
	var (
		nickname  string
		twitterId string
		err       error
		request   Request
		setupData *entities.SetupData
		player    *entities.Player
		twitter   *anaconda.TwitterApi
	)

	if err = websocket.JSON.Receive(ws, &request); err != nil {
		return nil, nil, err
	}

	if len(request.Username) <= 0 || len(request.TwitterID) <= 0 {
		return nil, nil, errors.New("Incomplete credentials")
	}

	if cfg.Twitter.SecureLogin {
		var ok bool
		anaconda.SetConsumerKey(cfg.Twitter.ConsumerKey)
		anaconda.SetConsumerSecret(cfg.Twitter.ConsumerSecret)
		twitter = anaconda.NewTwitterApi(request.AccessToken, request.AccessTokenSecret)
		if ok, err = twitter.VerifyCredentials(); !ok {
			return nil, nil, err
		}
	}

	serverParamsMessage := response.NewServerParams()
	if err = websocket.JSON.Send(ws, &serverParamsMessage); err != nil {
		return nil, nil, err
	}

	nickname = request.Username
	twitterId = request.TwitterID

	entity, _ := entities.Get(fmt.Sprintf("player.%s", nickname))
	if entity == nil {
		setupData, err = FetchSetupData(ws)
		if err != nil {
			return nil, nil, err
		}
		player = register(setupData, nickname, twitterId, twitter)
	} else {
		player = entity.(*entities.Player)
	}
	return player, twitter, nil
}
func fetchMissionTarget(targetKey string) (*entities.Planet, *response.StateChange, error) {
	targetEntity, err := entities.Get(targetKey)
	if err != nil {
		return nil, nil, err
	}
	target := targetEntity.(*entities.Planet)
	target.UpdateShipCount()

	stateChange := response.NewStateChange()
	stateChange.RawPlanets = map[string]*entities.Planet{
		target.Key(): target,
	}
	clients.Broadcast(target)

	return target, stateChange, nil
}
Example #5
0
// This function makes all the checks needed for creation of a new mission.
func parseAction(request *Request) error {
	var err error

	sendMissionMessage := response.NewSendMissions()

	defer func() error {
		if panicked := recover(); panicked != nil {
			err = errors.New("Parse action panic!")
		}
		return err
	}()

	if len(request.StartPlanets) == 0 {
		errorMessage := "No start planets provided"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}

	target, err := entities.Get(request.EndPlanet)
	if err != nil {
		errorMessage := "End planet does not exist"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}
	endPlanet := target.(*entities.Planet)

	if _, isMissionTypeValid := missionTypes[request.Type]; !isMissionTypeValid {
		errorMessage := "Invalid mission type!"
		sendMissionMessage.FailedMissions["Global"] = errorMessage
		return errors.New(errorMessage)
	}

	for _, startPlanet := range request.StartPlanets {

		mission, err := prepareMission(startPlanet, endPlanet, request)

		if err == nil {
			sendMissionMessage.Missions[mission.Key()] = mission
		} else {
			sendMissionMessage.FailedMissions[startPlanet] = err.Error()
		}
	}

	request.Client.Send(sendMissionMessage)

	return nil
}
Example #6
0
// This function is called from the message handler to parse the first message for every new connection.
// It check for existing user in the DB and logs him if the password is correct.
// If the user is new he is initiated and a new home planet nad solar system are generated.
func login(ws *websocket.Conn) (*Client, response.Responser, error) {
	player, twitter, err := authenticate(ws)
	if err != nil {
		return nil, response.NewLoginFailed(), err
	}

	client := NewClient(ws, player, twitter)
	homePlanetEntity, err := entities.Get(player.HomePlanet)
	if err != nil {
		return nil, nil, errors.New("Player's home planet is missing!")
	}
	homePlanet := homePlanetEntity.(*entities.Planet)

	loginSuccess := response.NewLoginSuccess(player, homePlanet)
	planetEntities := entities.Find("planet.*")
	planets := make([]*entities.Planet, 0, len(planetEntities))
	sites := make([]voronoi.Vertex, 0, len(planetEntities))
	x0, xn, y0, yn := 0.0, 0.0, 0.0, 0.0
	for i, planetEntity := range planetEntities {
		planets = append(planets, planetEntity.(*entities.Planet))
		if x0 > planets[i].Position.X {
			x0 = planets[i].Position.X
		}
		if xn < planets[i].Position.X {
			xn = planets[i].Position.X
		}
		if y0 > planets[i].Position.Y {
			y0 = planets[i].Position.Y
		}
		if yn < planets[i].Position.Y {
			yn = planets[i].Position.Y
		}
		sites = append(sites, voronoi.Vertex{planets[i].Position.X, planets[i].Position.Y})
	}

	bbox := voronoi.NewBBox(x0, xn, y0, yn)

	response.Diagram = voronoi.ComputeDiagram(sites, bbox, true)
	return client, loginSuccess, nil
}
// Spawns missionary for all mission records found
// in the database when the server is started
func SpawnDbMissions() {
	for _, entity := range entities.Find("mission.*") {
		mission, ok := entity.(*entities.Mission)
		if !ok {
			log.Printf("Record %s does not seem to be a mission!?\n", mission.Key())
		}

		sourceKey := fmt.Sprintf("planet.%s", mission.Source.Name)
		sourceEntity, err := entities.Get(sourceKey)
		if err != nil {
			log.Printf("Can't find planet %s for mission %s!?\n", sourceKey, mission.Key())
		}
		source := sourceEntity.(*entities.Planet)

		mission.SetAreaSet(source.AreaSet())
		log.Printf(
			"Spawning %s's mission from %s to %s...\n",
			mission.Player,
			mission.Source.Name,
			mission.Target.Name,
		)
		go StartMissionary(mission)
	}
}
Example #8
0
func prepareMission(startPlanet string, endPlanet *entities.Planet, request *Request) (*entities.Mission, error) {
	sourceEntity, err := entities.Get(startPlanet)
	if err != nil {
		return nil, err
	}

	source := sourceEntity.(*entities.Planet)

	if source.Owner != request.Client.Player.Username {
		return nil, errors.New("The mission owner does not own the start planet.")
	}

	if startPlanet == request.EndPlanet {
		return nil, errors.New("Start and end planet are the same.")
	}

	mission := request.Client.Player.StartMission(
		source,
		endPlanet,
		request.Path,
		request.Fleet,
		request.Type,
	)

	if mission.ShipCount == 0 {
		return nil, errors.New("Not enough pilots on source planet!")
	}

	entities.Save(source)
	go StartMissionary(mission)
	entities.Save(mission)
	clients.Broadcast(mission)
	clients.Broadcast(source)

	return mission, nil
}
// StartMissionary is used when a call to initiate a new mission is rescived.
// 1. When the delay ends the thread ends the mission calling EndMission
// 2. The end of the mission is bradcasted to all clients and the mission entry is erased from the DB.
func StartMissionary(mission *entities.Mission) {
	var (
		err             error
		excessShips     int32
		ownerHasChanged bool
		foundStartPoint bool
		player          *entities.Player
		stateChange     *response.StateChange
		target          *entities.Planet
		timeSlept       time.Duration
	)

	initialTimeSlept := time.Duration(time.Now().UnixNano()/1e6 - mission.StartTime)
	if initialTimeSlept > 0 {
		timeSlept = initialTimeSlept
	} else {
		foundStartPoint = true
	}

	entities.Save(mission)
	targetKey := fmt.Sprintf("planet.%s", mission.Target.Name)
	for _, transferPoint := range mission.TransferPoints() {
		if !foundStartPoint {
			if initialTimeSlept > transferPoint.TravelTime {
				initialTimeSlept -= transferPoint.TravelTime
				mission.ChangeAreaSet(transferPoint.CoordinateAxis, transferPoint.Direction)
				continue
			} else {
				foundStartPoint = true
			}
		}

		timeToSleep := transferPoint.TravelTime
		timeSlept += timeToSleep
		time.Sleep(timeToSleep * time.Millisecond)
		mission.ChangeAreaSet(transferPoint.CoordinateAxis, transferPoint.Direction)

		clients.Broadcast(mission)
	}

	time.Sleep((mission.TravelTime - timeSlept) * time.Millisecond)
	target, stateChange, err = fetchMissionTarget(targetKey)
	if err != nil {
		log.Print("fetchMissionTarget fail: ", err.Error())
		return
	}
	ownerBeforeMission := target.Owner

	if ownerBeforeMission == "" {
		player = nil
	} else {
		playerEntity, pErr := entities.Get(fmt.Sprintf("player.%s", ownerBeforeMission))
		if pErr != nil {
			log.Println("Error in target planet owner fetch:", pErr.Error())
			return
		}
		player = playerEntity.(*entities.Player)
	}

	switch mission.Type {
	case "Attack":
		if err != nil {
			log.Print("Error in target planet fetch:", err.Error())
		}
		excessShips, ownerHasChanged = mission.EndAttackMission(target)
		clients.Broadcast(target)
	case "Supply":
		if err != nil {
			log.Print("Error in target planet fetch:", err.Error())
		}
		excessShips, ownerHasChanged = mission.EndSupplyMission(target)
		if player != nil {
			clients.Send(player, stateChange)
		}
	case "Spy":
		for {
			if err != nil {
				log.Print("Error in target planet fetch:", err.Error())
			}
			// All spy pilots die if planet is overtaken (they are killed)
			// Other possible solution is to generate a supply mission back (they flee)
			if target.Owner != mission.Target.Owner {
				break
			}
			mission.EndSpyMission(target)
			updateSpyReports(mission, stateChange)
			if mission.ShipCount > 0 {
				time.Sleep(entities.Settings.SpyReportValidity * time.Second)
			} else {
				break
			}
			target, stateChange, err = fetchMissionTarget(targetKey)
		}
		time.Sleep(entities.Settings.SpyReportValidity * time.Second)
		updateSpyReports(mission, stateChange)
	}

	entities.RemoveFromArea(mission.Key(), mission.AreaSet())
	entities.Delete(mission.Key())
	entities.Save(target)

	if ownerHasChanged {
		go func(owned, owner string) {
			leaderBoard.Channel <- [2]string{owned, owner}
		}(ownerBeforeMission, target.Owner)

		if player != nil {
			ownerChange := response.NewOwnerChange()
			ownerChange.RawPlanet = map[string]*entities.Planet{
				target.Key(): target,
			}
			clients.Send(player, ownerChange)
		}
	}

	if excessShips > 0 {
		startExcessMission(mission, target, excessShips)
	}
}