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) }
// 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 }
// 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 }
// 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 }
// 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) } }
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) } }