diff --git a/.gitignore b/.gitignore index e76cdaf..cc987d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -botserv +bserv/bserv *.swp tags diff --git a/main.go b/bserv/main.go similarity index 65% rename from main.go rename to bserv/main.go index c6b300b..2b83e2c 100644 --- a/main.go +++ b/bserv/main.go @@ -9,6 +9,7 @@ import ( "os" "runtime/pprof" "time" + "bitbucket.org/hackerbots/botserv" "code.google.com/p/go.net/websocket" ) @@ -22,12 +23,6 @@ var config = flag.String("config", "~/.config/hackerbots/config.json", "location var delta float32 -var idg *IdGenerator -var conf Config - -// This is the main, global collection of games -var games MapLock - func main() { log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) log.Printf("Starting Server...") @@ -49,10 +44,7 @@ func main() { log.Println("serving profile info at http://localhost:8667/debug/pprof/") } - games = MapLock{m: make(map[string]*game)} - idg = NewIdGenerator() - - conf, err = loadConfig(*config) + conf, err := botserv.LoadConfig(*config) if err != nil { log.Fatal(err) } @@ -61,14 +53,24 @@ func main() { sm := http.NewServeMux() - sm.Handle("/", JsonHandler(index)) - sm.Handle("/ws/", websocket.Handler(addPlayer)) - sm.Handle("/game/start/", JsonHandler(startGame)) - sm.Handle("/game/list/", JsonHandler(listGames)) - sm.Handle("/game/stats/", JsonHandler(gameStats)) - sm.Handle("/game/bw/", JsonHandler(bw)) - sm.Handle("/game/stop/", JsonHandler(stopGame)) - sm.HandleFunc("/fuck/shit/up/", killServer) + c := botserv.Controller{ + Idg: botserv.NewIdGenerator(), + Conf: conf, + Games: botserv.MapLock{ + M: make(map[string]*botserv.Game), + }, + Memprofile: *mprofile, + Profile: *profile, + } + + sm.Handle("/", botserv.JsonHandler(c.Index)) + sm.Handle("/ws/", websocket.Handler(c.AddPlayer)) + sm.Handle("/game/start/", botserv.JsonHandler(c.StartGame)) + sm.Handle("/game/list/", botserv.JsonHandler(c.ListGames)) + sm.Handle("/game/stats/", botserv.JsonHandler(c.GameStats)) + sm.Handle("/game/bw/", botserv.JsonHandler(c.BW)) + sm.Handle("/game/stop/", botserv.JsonHandler(c.StopGame)) + sm.HandleFunc("/fuck/shit/up/", c.KillServer) err = http.ListenAndServe(*addr, sm) if err != nil { diff --git a/config.go b/config.go index 01e999b..7e12d15 100644 --- a/config.go +++ b/config.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "encoding/json" @@ -31,7 +31,7 @@ const ( DEFAULT_MODE = "deathmatch" ) -func loadConfig(filename string) (Config, error) { +func LoadConfig(filename string) (Config, error) { c := Config{ Tick: TICK, Timescale: TIMESCALE, diff --git a/control.go b/control.go index 9848740..a5708b7 100644 --- a/control.go +++ b/control.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "encoding/json" @@ -19,13 +19,21 @@ func (h JsonHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { h(w, req) } -func startGame(w http.ResponseWriter, req *http.Request) { +type Controller struct { + Idg *IdGenerator + Conf Config + Games MapLock + Memprofile string + Profile string +} + +func (c *Controller) StartGame(w http.ResponseWriter, req *http.Request) { log.Println("asked to create a game") - requested_game_name := idg.Hash() - width, height := float32(conf.Width), float32(conf.Height) + requested_game_name := c.Idg.Hash() + width, height := float32(c.Conf.Width), float32(c.Conf.Height) obstacles := 0 - maxPoints := conf.MaxPoints + maxPoints := c.Conf.MaxPoints mode := "deathmatch" // here we determine if we are going to run with defaults or pick them off @@ -55,10 +63,10 @@ func startGame(w http.ResponseWriter, req *http.Request) { mode = cfg.Mode } - g := games.get(requested_game_name) + g := c.Games.get(requested_game_name) if g == nil { log.Printf("Game '%s' non-existant; making it now", requested_game_name) - g, err := NewGame(requested_game_name, width, height, obstacles, conf.Tick, maxPoints, mode) + g, err := NewGame(requested_game_name, width, height, obstacles, c.Conf.Tick, maxPoints, mode) if err != nil { log.Printf("problem creating game: %s: %s", requested_game_name, err) b, _ := json.Marshal(NewFailure("game creation failure")) @@ -66,7 +74,7 @@ func startGame(w http.ResponseWriter, req *http.Request) { return } go g.run() - games.add(g) + c.Games.add(g) } else { log.Printf("Game '%s' already exists: %p", requested_game_name, g) b, _ := json.Marshal(NewFailure("game already exists")) @@ -84,10 +92,10 @@ func startGame(w http.ResponseWriter, req *http.Request) { } } -func listGames(w http.ResponseWriter, req *http.Request) { +func (c *Controller) ListGames(w http.ResponseWriter, req *http.Request) { log.Println("games list requested") - games.RLock() - defer games.RUnlock() + c.Games.RLock() + defer c.Games.RUnlock() type pout struct { Name string `json:"name"` Id string `json:"id"` @@ -97,7 +105,7 @@ func listGames(w http.ResponseWriter, req *http.Request) { Players []pout `json:"players"` } ids := make([]gl, 0) - for id, g := range games.m { + for id, g := range c.Games.M { players := make([]pout, 0) // TODO - players instead of robots? for p := range g.players { @@ -118,19 +126,19 @@ func listGames(w http.ResponseWriter, req *http.Request) { } } -func gameStats(w http.ResponseWriter, req *http.Request) { +func (c *Controller) GameStats(w http.ResponseWriter, req *http.Request) { // TODO: wrap this up in something similar to the JsonHandler to verify the // url? Look at gorilla routing? - key, err := getGameId(req.URL.Path) + key, err := c.getGameId(req.URL.Path) if err != nil { b, _ := json.Marshal(NewFailure(err.Error())) http.Error(w, string(b), http.StatusBadRequest) return } log.Printf("requested stats for game: %s", key) - games.RLock() - g, ok := games.m[key] - games.RUnlock() + c.Games.RLock() + g, ok := c.Games.M[key] + c.Games.RUnlock() if !ok { b, _ := json.Marshal(NewFailure("game not found")) http.Error(w, string(b), http.StatusNotFound) @@ -143,19 +151,19 @@ func gameStats(w http.ResponseWriter, req *http.Request) { } } -func bw(w http.ResponseWriter, req *http.Request) { +func (c *Controller) BW(w http.ResponseWriter, req *http.Request) { // TODO: wrap this up in something similar to the JsonHandler to verify the // url? Look at gorilla routing? - key, err := getGameId(req.URL.Path) + key, err := c.getGameId(req.URL.Path) if err != nil { b, _ := json.Marshal(NewFailure(err.Error())) http.Error(w, string(b), http.StatusBadRequest) return } log.Printf("requested bandwidth for game: %s", key) - games.RLock() - g, ok := games.m[key] - games.RUnlock() + c.Games.RLock() + g, ok := c.Games.M[key] + c.Games.RUnlock() if !ok { b, _ := json.Marshal(NewFailure("game not found")) http.Error(w, string(b), http.StatusNotFound) @@ -170,16 +178,16 @@ func bw(w http.ResponseWriter, req *http.Request) { } } -func stopGame(w http.ResponseWriter, req *http.Request) { - key, err := getGameId(req.URL.Path) +func (c *Controller) StopGame(w http.ResponseWriter, req *http.Request) { + key, err := c.getGameId(req.URL.Path) if err != nil { b, _ := json.Marshal(NewFailure(err.Error())) http.Error(w, string(b), http.StatusBadRequest) return } - games.Lock() - g, ok := games.m[key] - defer games.Unlock() + c.Games.Lock() + g, ok := c.Games.M[key] + defer c.Games.Unlock() if !ok { http.NotFound(w, req) return @@ -197,15 +205,15 @@ func stopGame(w http.ResponseWriter, req *http.Request) { } } -func killServer(w http.ResponseWriter, req *http.Request) { - if *profile != "" { +func (c *Controller) KillServer(w http.ResponseWriter, req *http.Request) { + if c.Profile != "" { log.Print("trying to stop cpu profile") pprof.StopCPUProfile() log.Print("stopped cpu profile") } - if *mprofile != "" { + if c.Memprofile != "" { log.Print("trying to dump memory profile") - f, err := os.Create(*mprofile) + f, err := os.Create(c.Memprofile) if err != nil { log.Fatal(err) } @@ -216,7 +224,7 @@ func killServer(w http.ResponseWriter, req *http.Request) { log.Fatal("shit got fucked up") } -func index(w http.ResponseWriter, req *http.Request) { +func (c *Controller) Index(w http.ResponseWriter, req *http.Request) { log.Println("version requested") version := struct { Version string `json:"version"` @@ -230,7 +238,7 @@ func index(w http.ResponseWriter, req *http.Request) { } } -func getGameId(path string) (string, error) { +func (c *Controller) getGameId(path string) (string, error) { var err error trimmed := strings.Trim(path, "/") fullPath := strings.Split(trimmed, "/") diff --git a/deathmatch.go b/deathmatch.go index 1848b7c..12150b2 100644 --- a/deathmatch.go +++ b/deathmatch.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "log" @@ -7,10 +7,10 @@ import ( type deathmatch struct { } -func (g *deathmatch) setup(gg *game) { +func (g *deathmatch) setup(gg *Game) { } -func (g *deathmatch) gameOver(gg *game) (bool, *GameOver) { +func (g *deathmatch) gameOver(gg *Game) (bool, *GameOver) { over := false var stats *GameOver @@ -41,5 +41,5 @@ func (g *deathmatch) gameOver(gg *game) (bool, *GameOver) { return over, stats } -func (g *deathmatch) tick(gg *game, payload *Boardstate) { +func (g *deathmatch) tick(gg *Game, payload *Boardstate) { } diff --git a/game.go b/game.go index a2b607a..8593db5 100644 --- a/game.go +++ b/game.go @@ -1,4 +1,4 @@ -package main +package botserv // delete me @@ -24,23 +24,23 @@ type Scanner struct { } type MapLock struct { - m map[string]*game + M map[string]*Game sync.RWMutex } // get is a function that returns a game if found, and creates one if // not found and force is true. In order to get a hash (rather than use // the string you pass) send "" for id. -func (ml *MapLock) get(id string) *game { +func (ml *MapLock) get(id string) *Game { ml.Lock() - g, _ := ml.m[id] + g, _ := ml.M[id] ml.Unlock() return g } -func (ml *MapLock) add(g *game) { +func (ml *MapLock) add(g *Game) { ml.Lock() - ml.m[g.id] = g + ml.M[g.id] = g ml.Unlock() } @@ -64,7 +64,7 @@ type GameStats struct { sync.RWMutex } -type game struct { +type Game struct { id string players map[*player]bool projectiles map[*Projectile]bool @@ -87,15 +87,18 @@ type game struct { stats GameStats mode GameMode bw *bandwidth.Bandwidth + Conf Config + Games *MapLock + Verbose bool } type GameMode interface { - setup(g *game) - tick(gg *game, payload *Boardstate) - gameOver(gg *game) (bool, *GameOver) + setup(g *Game) + tick(gg *Game, payload *Boardstate) + gameOver(gg *Game) (bool, *GameOver) } -func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, mode string) (*game, error) { +func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, mode string) (*Game, error) { bw, err := bandwidth.NewBandwidth( []int{1, 10, 60}, 1*time.Second, @@ -105,7 +108,7 @@ func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, m return nil, err } go bw.Run() - g := &game{ + g := &Game{ id: id, register: make(chan *player, maxPlayer), unregister: make(chan *player, maxPlayer), @@ -141,7 +144,7 @@ func NewGame(id string, width, height float32, obstacles, tick, maxPoints int, m return g, nil } -func (g *game) tick(payload *Boardstate) { +func (g *Game) tick(payload *Boardstate) { g.players_remaining = 0 payload.Objects = MinifyObstacles(g.obstacles) @@ -196,7 +199,7 @@ func (g *game) tick(payload *Boardstate) { } } -func (g *game) sendUpdate(payload *Boardstate) { +func (g *Game) sendUpdate(payload *Boardstate) { // Ensure that the robots are always sent in a consistent order sort.Sort(RobotSorter{Robots: payload.OtherRobots}) sort.Sort(AllRobotSorter{Robots: payload.AllBots}) @@ -292,20 +295,20 @@ func (g *game) sendUpdate(payload *Boardstate) { } -func (g *game) run() { +func (g *Game) run() { var t0, t1 time.Time - ticker := time.NewTicker(time.Duration(conf.Tick) * time.Millisecond) + ticker := time.NewTicker(time.Duration(g.Conf.Tick) * time.Millisecond) for { select { case <-g.kill: log.Printf("game %s: received kill signal, dying gracefully", g.id) close(g.bw.Quit) - games.Lock() + g.Games.Lock() for player := range g.players { close(player.send) } - delete(games.m, g.id) - games.Unlock() + delete(g.Games.M, g.id) + g.Games.Unlock() return case p := <-g.register: g.players[p] = true @@ -330,7 +333,7 @@ func (g *game) run() { g.turn++ payload.Turn = g.turn - if *verbose { + if g.Verbose { log.Printf("\033[2JTurn: %v", g.turn) log.Printf("Players: %v", len(g.players)) log.Printf("Projectiles: %v", len(g.projectiles)) @@ -346,7 +349,7 @@ func (g *game) run() { g.mode.tick(g, payload) t1 = time.Now() - if *verbose { + if g.Verbose { log.Printf("Turn Processes %v\n", t1.Sub(t0)) } @@ -354,14 +357,14 @@ func (g *game) run() { g.sendUpdate(payload) t1 = time.Now() - if *verbose { + if g.Verbose { log.Printf("Sent Payload %v\n", t1.Sub(t0)) } } } } -func (g *game) sendGameOver(eg *GameOver) { +func (g *Game) sendGameOver(eg *GameOver) { log.Printf("sending out game over message: %+v", eg) for p := range g.players { p.send <- eg @@ -373,7 +376,7 @@ func (g *game) sendGameOver(eg *GameOver) { // returns a GameParam object popuplated by info from the game. This is // used during client/server initial negociation. -func (g *game) gameParam() *GameParam { +func (g *Game) gameParam() *GameParam { return &GameParam{ BoardSize: BoardSize{ Width: g.width, diff --git a/id.go b/id.go index 0b338fb..ad22b6e 100644 --- a/id.go +++ b/id.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "crypto/md5" diff --git a/id_test.go b/id_test.go index 49f8206..cb900c1 100644 --- a/id_test.go +++ b/id_test.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "testing" diff --git a/melee.go b/melee.go index 670bf09..a524b1c 100644 --- a/melee.go +++ b/melee.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "log" @@ -9,15 +9,15 @@ type melee struct { respawn_timer float64 } -func (g *melee) setup(gg *game) { +func (g *melee) setup(gg *Game) { g.respawn_timer = 5000 } -func (g *melee) gameOver(gg *game) (bool, *GameOver) { +func (g *melee) gameOver(gg *Game) (bool, *GameOver) { return false, &GameOver{} } -func (g *melee) tick(gg *game, payload *Boardstate) { +func (g *melee) tick(gg *Game, payload *Boardstate) { for p := range gg.players { for _, r := range p.Robots { _, ok := g.respawn[r] diff --git a/obstacle.go b/obstacle.go index 0149c11..0b4410a 100644 --- a/obstacle.go +++ b/obstacle.go @@ -1,9 +1,10 @@ -package main +package botserv import ( - v "bitbucket.org/hackerbots/vector" "math" "math/rand" + + v "bitbucket.org/hackerbots/vector" ) type Obstacle struct { diff --git a/player.go b/player.go index b28a30c..50b606a 100644 --- a/player.go +++ b/player.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "encoding/gob" diff --git a/projectile.go b/projectile.go index 2ef2d34..5d94f46 100644 --- a/projectile.go +++ b/projectile.go @@ -1,8 +1,9 @@ -package main +package botserv import ( - v "bitbucket.org/hackerbots/vector" "log" + + v "bitbucket.org/hackerbots/vector" ) type Projectile struct { @@ -13,12 +14,13 @@ type Projectile struct { Speed float32 `json:"-"` Damage int `json:"-"` Owner *Robot `json:"-"` + Delta float32 } -func (p *Projectile) Tick(g *game) { +func (p *Projectile) Tick(g *Game) { vec := p.MoveTo.Sub(p.Position) v_norm := vec.Normalize() - v_scaled := v_norm.Scale(p.Speed * delta) + v_scaled := v_norm.Scale(p.Speed * p.Delta) newPos := p.Position.Add(v_scaled) hit_player := false diff --git a/protocol.go b/protocol.go index f9ea4ff..2ebbf2b 100644 --- a/protocol.go +++ b/protocol.go @@ -1,4 +1,4 @@ -package main +package botserv import ( "log" @@ -143,7 +143,7 @@ func NewFailure(reason string) *Failure { } } -func addPlayer(ws *websocket.Conn) { +func (c *Controller) AddPlayer(ws *websocket.Conn) { var gid GameID err := websocket.JSON.Receive(ws, &gid) if err != nil { @@ -151,16 +151,16 @@ func addPlayer(ws *websocket.Conn) { return } - game := games.get(gid.Id) + game := c.Games.get(gid.Id) if game == nil { var err error game, err = NewGame( gid.Id, - float32(conf.Width), - float32(conf.Height), - conf.Obstacles, - conf.Tick, - conf.MaxPoints, + float32(c.Conf.Width), + float32(c.Conf.Height), + c.Conf.Obstacles, + c.Conf.Tick, + c.Conf.MaxPoints, "", ) if err != nil { @@ -169,10 +169,10 @@ func addPlayer(ws *websocket.Conn) { return } go game.run() - games.add(game) + c.Games.add(game) } - player_id := idg.Hash() + player_id := c.Idg.Hash() err = websocket.JSON.Send(ws, NewPlayerID(player_id)) if err != nil { log.Printf("game %s: unable to send player_id to player %s", gid.Id, player_id) @@ -284,7 +284,7 @@ encodingLoops: convertedStats[name] = dstat r := Robot{ Stats: dstat, - Id: idg.Hash(), + Id: c.Idg.Hash(), Name: name, Health: 10, Heading: v.Vector2d{1, 0}, diff --git a/robot.go b/robot.go index 79639ba..8f37d3c 100644 --- a/robot.go +++ b/robot.go @@ -1,10 +1,11 @@ -package main +package botserv import ( - v "bitbucket.org/hackerbots/vector" "log" "math" "math/rand" + + v "bitbucket.org/hackerbots/vector" ) type Robot struct { @@ -30,6 +31,8 @@ type Robot struct { Probe *v.Point2d `json:"probe"` ProbeResult *Collision `json:"probe_result"` gameStats *BotStats `json:-` + Delta float32 `json:-` + Idg *IdGenerator } type Collision struct { @@ -172,7 +175,7 @@ type Instruction struct { // returns collision, the intersection point, and the robot with whom r has // collided, if this happened. -func (r *Robot) checkCollisions(g *game, probe v.Vector2d) (bool, *v.Point2d, *Robot) { +func (r *Robot) checkCollisions(g *Game, probe v.Vector2d) (bool, *v.Point2d, *Robot) { finalCollision := false collision := false closest := float32(math.Inf(1)) @@ -243,19 +246,19 @@ func (r *Robot) checkCollisions(g *game, probe v.Vector2d) (bool, *v.Point2d, *R return finalCollision, intersection, finalRobot } -func (r *Robot) Tick(g *game) { +func (r *Robot) Tick(g *Game) { r.Collision = nil r.Hit = false r.scan(g) // Adjust Speed if r.Speed < r.TargetSpeed { - r.Speed += (r.Stats.Acceleration * delta) + r.Speed += (r.Stats.Acceleration * r.Delta) if r.Speed > r.TargetSpeed { r.Speed = r.TargetSpeed } } else if float32(math.Abs(float64(r.Speed-r.TargetSpeed))) > v.Epsilon { - r.Speed -= (r.Stats.Acceleration * delta) + r.Speed -= (r.Stats.Acceleration * r.Delta) // Cap reverse to 1/2 speed if r.Speed < (-0.5 * r.TargetSpeed) { r.Speed = (-0.5 * r.TargetSpeed) @@ -292,15 +295,15 @@ func (r *Robot) Tick(g *game) { } // Max turn radius in this case is in degrees per second - if float32(math.Abs(float64(angle))) > (float32(r.Stats.TurnSpeed) * delta) { + if float32(math.Abs(float64(angle))) > (float32(r.Stats.TurnSpeed) * r.Delta) { // New heading should be a little less, take current heading and // rotate by the max turn radius per frame. - rot := (float32(r.Stats.TurnSpeed) * delta) * v.Deg2rad + rot := (float32(r.Stats.TurnSpeed) * r.Delta) * v.Deg2rad new_heading = current_heading.Rotate(rot * float32(dir)) } - move_vector := new_heading.Scale(r.Speed * delta) + move_vector := new_heading.Scale(r.Speed * r.Delta) collision, intersection_point, hit_robot := r.checkCollisions(g, move_vector) if collision { dmg := int(math.Abs(float64(r.Speed)) / 10.0) @@ -352,7 +355,7 @@ func (r *Robot) Tick(g *game) { // We only self repair when we're stopped if math.Abs(float64(r.Speed)) < v.Epsilon && r.RepairCounter > 0 { - r.RepairCounter -= delta + r.RepairCounter -= r.Delta if r.RepairCounter < 0 { r.Health += g.repair_hp if r.Health > r.Stats.Hp { @@ -364,9 +367,9 @@ func (r *Robot) Tick(g *game) { // We are only allowed to scan when we're stopped if math.Abs(float64(r.Speed)) < v.Epsilon && r.ActiveScan { - r.ScanCounter += delta * float32(r.Stats.ScannerRadius) * 0.1 + r.ScanCounter += r.Delta * float32(r.Stats.ScannerRadius) * 0.1 } else if r.ScanCounter > 0 { - r.ScanCounter -= delta * float32(r.Stats.ScannerRadius) * 0.05 + r.ScanCounter -= r.Delta * float32(r.Stats.ScannerRadius) * 0.05 if r.ScanCounter <= 0 { r.ScanCounter = 0 } @@ -394,7 +397,7 @@ func (r *Robot) Tick(g *game) { } } -func (r *Robot) scan(g *game) { +func (r *Robot) scan(g *Game) { r.Scanners = r.Scanners[:0] for player := range g.players { for _, bot := range player.Robots { @@ -440,9 +443,9 @@ func (r *Robot) scan(g *game) { } -func (r *Robot) fire(g *game) *Projectile { +func (r *Robot) fire(g *Game) *Projectile { // Throttle the fire rate - time_since_fired := (float32(g.turn) * (delta * 1000)) - (float32(r.LastFired) * (delta * 1000)) + time_since_fired := (float32(g.turn) * (r.Delta * 1000)) - (float32(r.LastFired) * (r.Delta * 1000)) if time_since_fired < float32(r.Stats.FireRate) { return nil } @@ -451,7 +454,7 @@ func (r *Robot) fire(g *game) *Projectile { r.gameStats.Shots++ return &Projectile{ - Id: idg.Hash(), + Id: r.Idg.Hash(), Position: r.Position, MoveTo: *r.FireAt, Damage: r.Stats.WeaponDamage, @@ -461,7 +464,7 @@ func (r *Robot) fire(g *game) *Projectile { } } -func (r *Robot) reset(g *game) { +func (r *Robot) reset(g *Game) { for { start_pos := v.Point2d{ X: rand.Float32() * float32(g.width), diff --git a/robot_test.go b/robot_test.go index d3dceb1..204424d 100644 --- a/robot_test.go +++ b/robot_test.go @@ -1,10 +1,11 @@ -package main +package botserv import ( - v "bitbucket.org/hackerbots/vector" "log" "math" "testing" + + v "bitbucket.org/hackerbots/vector" ) func init() { diff --git a/splosion.go b/splosion.go index 612f8af..7dd74d4 100644 --- a/splosion.go +++ b/splosion.go @@ -1,4 +1,4 @@ -package main +package botserv import ( v "bitbucket.org/hackerbots/vector"