From 8203296fd805c8446427ebfe3b0568041539d3e8 Mon Sep 17 00:00:00 2001 From: aerth Date: Sat, 9 Jul 2022 15:02:48 +0000 Subject: [PATCH] refactor, generate random map every play --- cheats.go | 6 - cmd/aerpg/main.go | 20 +- cmd/mapgen/main.go | 150 +--------- cmd/mapmaker/main.go | 41 +-- character.go => librpg/character.go | 3 +- librpg/cheats.go | 8 + color_test.go => librpg/color_test.go | 0 librpg/common/object.go | 131 +++++++++ librpg/common/objects.go | 163 +++++++++++ librpg/common/path.go | 61 ++++ patterns.go => librpg/common/patterns.go | 8 +- librpg/common/rand.go | 21 ++ cursor.go => librpg/cursor.go | 0 dynamic.go => librpg/dynamic.go | 8 +- effects.go => librpg/effects.go | 0 inventory.go => librpg/inventory.go | 0 item_test.go => librpg/item_test.go | 0 items.go => librpg/items.go | 5 +- living.go => librpg/living.go | 92 +++++- load.go => librpg/load.go | 0 librpg/maps/map.go | 151 ++++++++++ librpg/maps/miniworld.go | 35 +++ menu.go => librpg/menu.go | 7 +- mob_test.go => librpg/mob_test.go | 2 +- names.go => librpg/names.go | 0 names_test.go => librpg/names_test.go | 0 librpg/newworld.go | 114 ++++++++ librpg/patterns.go | 10 + regions.go => librpg/regions.go | 10 +- spells.go => librpg/spells.go | 0 stats.go => librpg/stats.go | 0 strings.go => librpg/strings.go | 10 - text.go => librpg/text.go | 0 title.go => librpg/title.go | 0 util.go => librpg/util.go | 0 world.go => librpg/world.go | 75 +++-- makefile | 12 +- objects.go | 351 ----------------------- path.go | 132 --------- sequence.go | 61 ---- 40 files changed, 893 insertions(+), 794 deletions(-) delete mode 100644 cheats.go rename character.go => librpg/character.go (98%) create mode 100644 librpg/cheats.go rename color_test.go => librpg/color_test.go (100%) create mode 100644 librpg/common/object.go create mode 100644 librpg/common/objects.go create mode 100644 librpg/common/path.go rename patterns.go => librpg/common/patterns.go (97%) create mode 100644 librpg/common/rand.go rename cursor.go => librpg/cursor.go (100%) rename dynamic.go => librpg/dynamic.go (72%) rename effects.go => librpg/effects.go (100%) rename inventory.go => librpg/inventory.go (100%) rename item_test.go => librpg/item_test.go (100%) rename items.go => librpg/items.go (97%) rename living.go => librpg/living.go (79%) rename load.go => librpg/load.go (100%) create mode 100644 librpg/maps/map.go create mode 100644 librpg/maps/miniworld.go rename menu.go => librpg/menu.go (89%) rename mob_test.go => librpg/mob_test.go (87%) rename names.go => librpg/names.go (100%) rename names_test.go => librpg/names_test.go (100%) create mode 100644 librpg/newworld.go create mode 100644 librpg/patterns.go rename regions.go => librpg/regions.go (71%) rename spells.go => librpg/spells.go (100%) rename stats.go => librpg/stats.go (100%) rename strings.go => librpg/strings.go (86%) rename text.go => librpg/text.go (100%) rename title.go => librpg/title.go (100%) rename util.go => librpg/util.go (100%) rename world.go => librpg/world.go (79%) delete mode 100644 objects.go delete mode 100644 path.go delete mode 100644 sequence.go diff --git a/cheats.go b/cheats.go deleted file mode 100644 index 34bd9b4..0000000 --- a/cheats.go +++ /dev/null @@ -1,6 +0,0 @@ -package rpg - -func (w *World) RandomLootSomewhere() { - loot := createLoot() - w.NewLoot(FindRandomTile(w.Tiles), []Item{loot}) -} diff --git a/cmd/aerpg/main.go b/cmd/aerpg/main.go index 71ebb7b..0a34e00 100644 --- a/cmd/aerpg/main.go +++ b/cmd/aerpg/main.go @@ -14,7 +14,8 @@ import ( // _ "image/png" - "github.com/aerth/rpg" + rpg "github.com/aerth/rpc/librpg" + "github.com/aerth/rpc/librpg/common" "github.com/faiface/pixel" "github.com/faiface/pixel/imdraw" "github.com/faiface/pixel/pixelgl" @@ -26,7 +27,8 @@ func init() { var ( flagenemies = flag.Int("e", 2, "number of enemies to begin with") - flaglevel = flag.String("test", "1", "custom world test (filename)") + flaglevel = flag.String("test", "", "custom world test (filename)") + flagseed = flag.String("seed", fmt.Sprintf("%d", rand.Int63()), "new random world") debug = flag.Bool("v", false, "extra logs") ) @@ -125,7 +127,7 @@ func run() { cursorsprite := rpg.GetCursor(1) // world generate - world := rpg.NewWorld(*flaglevel, *flagenemies) + world := rpg.NewWorld(*flaglevel, *flagenemies, *flagseed) if world == nil { return } @@ -154,7 +156,7 @@ func run() { // draw menu bar menubatch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) - rpg.DrawPattern(menubatch, spritemap[67], pixel.R(0, 0, win.Bounds().W()+20, 60), 100) + common.DrawPattern(menubatch, spritemap[67], pixel.R(0, 0, win.Bounds().W()+20, 60), 100) for _, btn := range buttons { spritemap[200].Draw(menubatch, IM.Moved(btn.Frame.Center())) } @@ -246,7 +248,7 @@ MainLoop: menubatch.Clear() win.Update() newbounds := win.Bounds() - rpg.DrawPattern(menubatch, spritemap[67], pixel.R(0, 0, newbounds.Max.X+20, 60), 100) + common.DrawPattern(menubatch, spritemap[67], pixel.R(0, 0, newbounds.Max.X+20, 60), 100) for _, btn := range buttons { spritemap[200].Draw(menubatch, IM.Moved(btn.Frame.Center())) } @@ -256,7 +258,7 @@ MainLoop: // teleport random if win.JustPressed(pixelgl.Key8) { - world.Char.Rect = rpg.DefaultSpriteRectangle.Moved(rpg.TileNear(world.Tiles, world.Char.Rect.Center()).Loc) + world.Char.Rect = common.DefaultSpriteRectangle.Moved(common.TileNear(world.Tiles, world.Char.Rect.Center()).Loc) } if win.JustPressed(pixelgl.KeyM) { world.NewMobs(1) @@ -271,7 +273,7 @@ MainLoop: // move all enemies (debug) if win.JustPressed(pixelgl.Key9) { for _, v := range world.Entities { - v.Rect = rpg.DefaultEntityRectangle.Moved(rpg.TileNear(world.Tiles, v.Rect.Center()).Loc) + v.Rect = rpg.DefaultEntityRectangle.Moved(common.TileNear(world.Tiles, v.Rect.Center()).Loc) } } if win.JustReleased(pixelgl.KeyI) { @@ -320,9 +322,9 @@ MainLoop: // highlight player tiles (left right up down and center) if *debug { for _, o := range world.Tile(world.Char.Rect.Center()).PathNeighbors() { - ob := o.(rpg.Object) + ob := o.(common.Object) ob.W = world - ob.Highlight(win, rpg.TransparentPurple) + ob.Highlight(win, common.TransparentPurple) } } diff --git a/cmd/mapgen/main.go b/cmd/mapgen/main.go index 8677e18..b329a63 100644 --- a/cmd/mapgen/main.go +++ b/cmd/mapgen/main.go @@ -1,154 +1,10 @@ package main import ( - "bytes" - "crypto/md5" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "math" - "math/rand" - "os" - "strconv" - "strings" - "time" - - "github.com/aerth/rpg" - astar "github.com/beefsack/go-astar" - "github.com/faiface/pixel" + "github.com/aerth/rpc/librpg/maps" ) -var BOUNDS float64 = 700 -var numbers = "0123456789" - -func init() { - rand.Seed(time.Now().UnixNano()) - // seed or random - if len(os.Args) == 2 { - if strings.HasPrefix(os.Args[1], "-h") { - fmt.Println("Usage:") - fmt.Println("\tmapgen [seed]") - fmt.Println("Example:") - fmt.Println("\tmapgen mycoolseed") - - os.Exit(111) - } - hashb := md5.Sum([]byte(os.Args[1])) - hash := []byte(fmt.Sprintf("%x", hashb)) - var seed []byte - for _, b := range hash { - if bytes.IndexAny([]byte{b}, numbers) != -1 { - log.Println(string(b), "is a number") - seed = append(seed, b) - } else { - log.Println(string(b), "is a letter") - } - - } - worldseed, err := strconv.ParseInt(string(seed), 10, 64) - if err != nil { - log.Println(err) - } - rand.Seed(worldseed) - log.Printf("Using world seed: %q -> %v", os.Args[1], worldseed) - log.Printf("Hash: %q", string(hash)) - } - // create maps dir if not exist - os.Mkdir("maps", 0755) -} - func main() { - olist := GenerateMap() - SaveMap(olist) -} - -func GenerateMap() []rpg.Object { - var olist []rpg.Object - t := rpg.O_TILE - for i := 0; i < 100; i++ { - - currentThing := 20 // grass - t = rpg.O_TILE - if i%3 == 0 { - currentThing = 53 // water - t = rpg.O_BLOCK - } - xmin := randfloat() - ymin := randfloat() - xmax := randfloat() - ymax := randfloat() - box := pixel.R(xmin, ymin, xmax, ymax).Norm() - log.Println(t, box) - pattern := rpg.DrawPatternObject(currentThing, t, box, 100) - for _, obj := range pattern { - if rpg.GetObjects(olist, obj.Loc) == nil { - olist = append(olist, obj) - } - } - - } - - // make dummy world for path finding - world := new(rpg.World) - world.Tiles = rpg.GetTiles(olist) - - // detect islands, make bridges - oldlist := olist - olist = nil - spot := world.Tile(rpg.FindRandomTile(oldlist)) - for _, o := range oldlist { - o.W = world - _, _, found := astar.Path(o, spot) - if o.Type == rpg.O_TILE && !found { - log.Println("found island tile", o) - } else { - olist = append(olist, o) - } - } - - // fill in with water blocks - waterworld := rpg.DrawPatternObject(53, rpg.O_BLOCK, pixel.R(-BOUNDS, -BOUNDS, BOUNDS, BOUNDS), 0) - for _, water := range waterworld { - if rpg.GetObjects(olist, water.Loc) == nil { - olist = append(olist, water) - } - } - - return olist -} - -func SaveMap(olist []rpg.Object) { - - b, err := json.Marshal(olist) - if err != nil { - log.Println(err) - return - } - f, err := ioutil.TempFile("maps", "map") - if err != nil { - log.Println(err) - return - } - _, err = f.Write(b) - if err != nil { - log.Println(err) - return - } - log.Println("saved file:", f.Name()) -} - -func randfloat() float64 { - step := 32.00 - f := float64(rand.Intn(int(BOUNDS))) - f = math.Floor(f) - f = float64(int(f/step)) * step - switch rand.Intn(2) { - case 0: - f = -f - default: - } - - return f - + olist := maps.GenerateMap("") + maps.SaveMap(olist) } diff --git a/cmd/mapmaker/main.go b/cmd/mapmaker/main.go index 7c5f552..ee95647 100644 --- a/cmd/mapmaker/main.go +++ b/cmd/mapmaker/main.go @@ -14,7 +14,8 @@ import ( "golang.org/x/image/colornames" - "github.com/aerth/rpg" + rpg "github.com/aerth/rpc/librpg" + "github.com/aerth/rpc/librpg/common" "github.com/faiface/pixel" "github.com/faiface/pixel/pixelgl" ) @@ -83,24 +84,24 @@ func run() { if err != nil { panic(err) } - var oldthings = []rpg.Object{} + var oldthings = []common.Object{} if b, err := ioutil.ReadFile(LEVEL); err == nil { err = json.Unmarshal(b, &oldthings) if err != nil { panic(err) } } - var things []rpg.Object + var things []common.Object for _, v := range oldthings { if *convert { log.Println("Converting") - v.Type = rpg.O_TILE - if v.SpriteNum == 53 && v.Type == rpg.O_TILE { - v.Type = rpg.O_BLOCK + v.Type = common.O_TILE + if v.SpriteNum == 53 && v.Type == common.O_TILE { + v.Type = common.O_BLOCK } } - v.Rect = rpg.DefaultSpriteRectangle.Moved(v.Loc) + v.Rect = common.DefaultSpriteRectangle.Moved(v.Loc) things = append(things, v) @@ -124,7 +125,7 @@ func run() { text := rpg.NewTextSmooth(14) fmt.Fprint(text, helpText) cursor := rpg.GetCursor(2) - undobuffer := []rpg.Object{} + undobuffer := []common.Object{} var turbo = false var highlight = true var box pixel.Rect @@ -177,8 +178,8 @@ func run() { } } - deleteThing := func(loc pixel.Vec) []rpg.Object { - var newthings []rpg.Object + deleteThing := func(loc pixel.Vec) []common.Object { + var newthings []common.Object for _, thing := range things { if thing.Rect.Contains(mouse) { log.Println("deleting:", thing) @@ -203,7 +204,7 @@ func run() { } else { if win.Pressed(pixelgl.KeyLeftShift) && win.Pressed(pixelgl.MouseButtonRight) || win.JustPressed(pixelgl.MouseButtonRight) { - thing := rpg.NewBlock(mouse) + thing := common.NewBlock(mouse) thing.SpriteNum = currentThing log.Println("Stamping Block", mouse, thing.SpriteNum) if replace { @@ -216,7 +217,7 @@ func run() { } if win.Pressed(pixelgl.KeyLeftShift) && win.Pressed(pixelgl.MouseButtonLeft) || win.JustPressed(pixelgl.MouseButtonLeft) { - thing := rpg.NewTile(mouse) + thing := common.NewTile(mouse) thing.SpriteNum = currentThing log.Println("Stamping Tile", mouse, thing.SpriteNum) if replace { @@ -275,13 +276,13 @@ func run() { box.Max = mouse box = box.Norm() log.Println("drawing rectangle:", box, currentThing) - things = append(DeleteThings(things, box), rpg.DrawPatternObject(currentThing, rpg.O_TILE, box, 100)...) + things = append(DeleteThings(things, box), common.DrawPatternObject(currentThing, common.O_TILE, box, 100)...) } if win.JustReleased(pixelgl.MouseButtonRight) { box.Max = mouse box = box.Norm() log.Println("drawing rectangle:", box, currentThing) - things = append(DeleteThings(things, box), rpg.DrawPatternObject(currentThing, rpg.O_BLOCK, box, 100)...) + things = append(DeleteThings(things, box), common.DrawPatternObject(currentThing, common.O_BLOCK, box, 100)...) } } @@ -290,14 +291,14 @@ func run() { for i := range things { things[i].Draw(batch, spritesheet, spritemap, 0) if highlight { - color := rpg.TransparentRed - if things[i].Type == rpg.O_TILE { - color = rpg.TransparentBlue + color := common.TransparentRed + if things[i].Type == common.O_TILE { + color = common.TransparentBlue } things[i].Highlight(batch, color) } if things[i].Rect.Contains(mouse) { - things[i].Highlight(batch, rpg.TransparentPurple) + things[i].Highlight(batch, common.TransparentPurple) } @@ -334,8 +335,8 @@ func main() { pixelgl.Run(run) } -func DeleteThings(from []rpg.Object, at pixel.Rect) []rpg.Object { - var cleaned []rpg.Object +func DeleteThings(from []common.Object, at pixel.Rect) []common.Object { + var cleaned []common.Object for _, o := range from { if !at.Contains(o.Loc) { cleaned = append(cleaned, o) diff --git a/character.go b/librpg/character.go similarity index 98% rename from character.go rename to librpg/character.go index b644ec0..0edef0e 100644 --- a/character.go +++ b/librpg/character.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/aerth/rpc/librpg/common" "github.com/faiface/pixel" "github.com/faiface/pixel/text" ) @@ -178,7 +179,7 @@ func (char *Character) Update(dt float64, dir Direction, world *World) { f2 := func(nexttile pixel.Rect) bool { for _, c := range world.Tiles { - if c.Type == O_TILE && c.Rect.Intersect(nexttile).Norm().Area() != 0 { + if c.Type == common.O_TILE && c.Rect.Intersect(nexttile).Norm().Area() != 0 { return true } } diff --git a/librpg/cheats.go b/librpg/cheats.go new file mode 100644 index 0000000..0a3a89a --- /dev/null +++ b/librpg/cheats.go @@ -0,0 +1,8 @@ +package rpg + +import "github.com/aerth/rpc/librpg/common" + +func (w *World) RandomLootSomewhere() { + loot := createLoot() + w.NewLoot(common.FindRandomTile(w.Tiles), []Item{loot}) +} diff --git a/color_test.go b/librpg/color_test.go similarity index 100% rename from color_test.go rename to librpg/color_test.go diff --git a/librpg/common/object.go b/librpg/common/object.go new file mode 100644 index 0000000..6599b0f --- /dev/null +++ b/librpg/common/object.go @@ -0,0 +1,131 @@ +package common + +import ( + "fmt" + "log" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/imdraw" + "golang.org/x/image/colornames" +) + +var DefaultSpriteRectangle = pixel.R(-16, -16, 16, 16) + +type ObjectType int + +const ( + O_NONE ObjectType = iota + O_TILE + O_BLOCK + O_INVISIBLE + O_SPECIAL + O_WIN + O_DYNAMIC // loot, doors +) + +type Object struct { + Loc pixel.Vec `json:"L"` + Rect pixel.Rect `json:"-"` + Type ObjectType `json:"T"` + P ObjectProperties `json:",omitempty"` + SpriteNum int `json:"S,omitempty"` + Sprite *pixel.Sprite `json:"-"` + W interface { + Tile(dot pixel.Vec) Object + } `json:"-"` + // Contains []Item `json:"-"` +} + +func (o Object) String() string { + return fmt.Sprintf("%s %s %s %v", o.Loc, o.Rect, o.Type, o.SpriteNum) +} + +type ObjectProperties struct { + Invisible bool `json:",omitempty"` + // Tile bool `json:",omitempty"` + // Block bool `json:",omitempty"` + Special bool `json:",omitempty"` +} + +func NewTile(loc pixel.Vec) Object { + return Object{ + Loc: loc, + Rect: pixel.Rect{loc.Sub(pixel.V(16, 16)), loc.Add(pixel.V(16, 16))}, + Type: O_TILE, + } +} +func NewBlock(loc pixel.Vec) Object { + return Object{ + Loc: loc, + Rect: pixel.Rect{loc.Sub(pixel.V(16, 16)), loc.Add(pixel.V(16, 16))}, + Type: O_BLOCK, + } +} + +func NewTileBox(rect pixel.Rect) Object { + return Object{ + Rect: rect, + Type: O_TILE, + } +} +func NewBlockBox(rect pixel.Rect) Object { + return Object{ + Rect: rect, + Type: O_BLOCK, + } +} + +var TransparentBlue = pixel.ToRGBA(colornames.Blue).Scaled(0.4) +var TransparentRed = pixel.ToRGBA(colornames.Red).Scaled(0.4) +var TransparentPurple = pixel.ToRGBA(colornames.Purple).Scaled(0.4) + +func (o Object) Highlight(win pixel.Target, color pixel.RGBA) { + imd := imdraw.New(nil) + imd.Color = color + imd.Push(o.Rect.Min, o.Rect.Max) + imd.Rectangle(1) + imd.Draw(win) +} +func (o Object) Draw(win pixel.Target, spritesheet pixel.Picture, sheetFrames []*pixel.Sprite, scaled float64) { + // r := pixel.Rect{o.Loc, o.w.Char.Rect.Center()} + // sz := r.Size() + // if sz.X > 1000 || sz.Y > 1000 { + // return + // } + if o.P.Invisible { + return + } + if o.Sprite == nil && o.Type != O_DYNAMIC { + if 0 > o.SpriteNum || o.SpriteNum > len(sheetFrames) { + log.Printf("unloadable sprite: %v/%v", o.SpriteNum, len(sheetFrames)) + return + } + o.Sprite = sheetFrames[o.SpriteNum] + } + if o.Sprite == nil && o.Type == O_DYNAMIC { + o.Sprite = sheetFrames[0] + } + // if o.Loc == pixel.ZV && o.Rect.Size().Y != 32 { + // log.Println(o.Rect.Size(), "cool rectangle", o.SpriteNum) + // DrawPattern(win, o.Sprite, o.Rect, 0) + // } else { + if scaled != 0.00 { + o.Sprite.Draw(win, pixel.IM.Scaled(pixel.ZV, scaled).Moved(o.Loc)) + return + } + + o.Sprite.Draw(win, pixel.IM.Moved(o.Loc)) + // } + +} + +const _ObjectType_name = "O_NONEO_TILEO_BLOCKO_INVISIBLEO_SPECIALO_WINO_DYNAMIC" + +var _ObjectType_index = [...]uint8{0, 6, 12, 19, 30, 39, 44, 53} + +func (i ObjectType) String() string { + if i < 0 || i >= ObjectType(len(_ObjectType_index)-1) { + return fmt.Sprintf("ObjectType(%d)", i) + } + return _ObjectType_name[_ObjectType_index[i]:_ObjectType_index[i+1]] +} diff --git a/librpg/common/objects.go b/librpg/common/objects.go new file mode 100644 index 0000000..20d04ad --- /dev/null +++ b/librpg/common/objects.go @@ -0,0 +1,163 @@ +package common + +import ( + "log" + "math/rand" + + "github.com/faiface/pixel" +) + +//var DefaultSpriteRectangle = pixel.R(-16, 0, 16, 32) +//var DefaultSpriteRectangle = pixel.R(-16, 0, 16, 32) + +// assumes only tiles are given +func FindRandomTile(os []Object) pixel.Vec { + if len(os) == 0 { + panic("no objects") + } + ob := os[rand.Intn(len(os))] + if ob.Loc != pixel.ZV && ob.SpriteNum != 0 && ob.Type == O_TILE { + return ob.Rect.Center() + } + return FindRandomTile(os) +} + +func GetObjects(objects []Object, position pixel.Vec) []Object { + var good []Object + for _, o := range objects { + if o.Rect.Contains(position) { + good = append(good, o) + } + } + return good +} + +func GetTiles(objects []Object) []Object { + var tiles []Object + for _, o := range objects { + if o.Type == O_TILE { + tiles = append(tiles, o) + } + } + return tiles +} + +func TilesAt(objects []Object, position pixel.Vec) []Object { + var good []Object + all := GetObjects(objects, position) + if len(all) > 0 { + for _, o := range all { + if DefaultSpriteRectangle.Moved(o.Loc).Contains(position) && o.Type == O_TILE { + good = append(good, o) + } + + } + } + return good + +} +func GetObjectsAt(objects []Object, position pixel.Vec) []Object { + var good []Object + all := GetObjects(objects, position) + if len(all) > 0 { + for _, o := range all { + if DefaultSpriteRectangle.Moved(o.Loc).Contains(position) { + good = append(good, o) + } + + } + } + return good + +} + +func GetBlocks(objects []Object, position pixel.Vec) []Object { + var bad []Object + all := GetObjects(objects, position) + if len(all) > 0 { + for _, o := range all { + if o.Type == O_BLOCK { + bad = append(bad, o) + } + + } + } + return bad +} + +// GetNeighbors gets the neighboring tiles +func (o Object) GetNeighbors() []Object { + neighbors := []Object{} + of := 32.0 + for _, offset := range [][]float64{ + {-of, 0}, + {of, 0}, + {0, -of}, + {0, of}, + } { + if n := o.W.Tile(pixel.V(o.Rect.Center().X+offset[0], o.Rect.Center().Y+offset[1])); n.Type == o.Type { + neighbors = append(neighbors, n) + } + } + return neighbors + +} +func TileNear(all []Object, loc pixel.Vec) Object { + tile := TilesAt(all, loc) + snap := 32.00 + loc.X = float64(int(loc.X/snap)) * snap + loc.Y = float64(int(loc.Y/snap)) * snap + radius := 1.00 + oloc := loc + if len(tile) > 0 { + oloc = tile[0].Loc + } + log.Println("looking for loc:", loc) + for i := 0; i < len(all); i++ { + loc.X += radius * 16 + loc.Y += radius * 16 + if loc == oloc { + continue + } + log.Println("Checking loc", loc) + os := TilesAt(all, loc) + if len(os) > 0 { + if os[0].Loc == pixel.ZV || os[0].Loc == oloc { + continue + } + return os[0] + } + loc2 := loc + loc2.X = -loc.X + os = TilesAt(all, loc.Scaled(-1)) + if len(os) > 0 { + if os[0].Loc == pixel.ZV || os[0].Loc == oloc { + continue + } + return os[0] + } + os = TilesAt(all, loc2.Scaled(1)) + if len(os) > 0 { + if os[0].Loc == pixel.ZV || os[0].Loc == oloc { + continue + } + return os[0] + } + + os = TilesAt(all, loc2.Scaled(-1)) + if len(os) > 0 { + if os[0].Loc == pixel.ZV || os[0].Loc == oloc { + continue + } + return os[0] + } + + if i%4 == 0 { + radius++ + loc.X += 16 + } + } + log.Println("not found") + return Object{Type: O_NONE} + +} diff --git a/librpg/common/path.go b/librpg/common/path.go new file mode 100644 index 0000000..788567a --- /dev/null +++ b/librpg/common/path.go @@ -0,0 +1,61 @@ +package common + +import ( + astar "github.com/beefsack/go-astar" + "github.com/faiface/pixel" +) + +func (o Object) PathNeighbors() []astar.Pather { + neighbors := []astar.Pather{} + of := 32.0 + //of = 24.0 + + for _, offset := range [][]float64{ + {-of, 0}, + {of, 0}, + {0, -of}, + {0, of}, + //{of, -of}, + //{-of, -of}, + //{of, of}, + //{-of, of}, + } { + n := o.W.Tile(pixel.V(o.Rect.Center().X+offset[0], o.Rect.Center().Y+offset[1])) + if n.Type == O_TILE { + neighbors = append(neighbors, n) + } + } + return neighbors +} + +func (o Object) PathEstimatedCost(to astar.Pather) float64 { + toT := to.(Object) + absX := toT.Rect.Center().X - o.Rect.Center().X + if absX < 0 { + absX = -absX + } + absY := toT.Rect.Center().Y - o.Rect.Center().Y + if absY < 0 { + absY = -absY + } + return float64(absX + absY) + +} + +func (o Object) PathNeighborCost(to astar.Pather) float64 { + toT := to.(Object) + cost := tileCosts[toT.Type] + return cost +} + +// KindCosts map tile kinds to movement costs. + +var tileCosts = map[ObjectType]float64{ + O_NONE: 30.00, + O_BLOCK: 30.00, + O_INVISIBLE: 3.00, + O_SPECIAL: 0.00, + O_TILE: 1.00, + O_WIN: 0.00, + O_DYNAMIC: 3.00, +} diff --git a/patterns.go b/librpg/common/patterns.go similarity index 97% rename from patterns.go rename to librpg/common/patterns.go index a8ff947..7e1b203 100644 --- a/patterns.go +++ b/librpg/common/patterns.go @@ -1,10 +1,8 @@ -package rpg +package common import ( "image/color" "log" - "math/rand" - "time" "github.com/faiface/pixel" "github.com/faiface/pixel/imdraw" @@ -12,10 +10,6 @@ import ( "golang.org/x/image/colornames" ) -func init() { - rand.Seed(time.Now().UnixNano()) -} - func DrawPatternObject(spritenum int, objecttype ObjectType, bounds pixel.Rect, width float64) []Object { var objects []Object size := pixel.Rect{pixel.V(-16, -16), pixel.V(16, 16)} diff --git a/librpg/common/rand.go b/librpg/common/rand.go new file mode 100644 index 0000000..2a9d144 --- /dev/null +++ b/librpg/common/rand.go @@ -0,0 +1,21 @@ +package common + +import ( + "math" + "math/rand" + + "github.com/faiface/pixel" +) + +func RandomColor() pixel.RGBA { + + r := rand.Float64() + g := rand.Float64() + b := rand.Float64() + len := math.Sqrt(r*r + g*g + b*b) + //if len == 0 { + // goto again + //} + return pixel.RGB(r/len, g/len, b/len) + +} diff --git a/cursor.go b/librpg/cursor.go similarity index 100% rename from cursor.go rename to librpg/cursor.go diff --git a/dynamic.go b/librpg/dynamic.go similarity index 72% rename from dynamic.go rename to librpg/dynamic.go index dfc7fec..60eba8e 100644 --- a/dynamic.go +++ b/librpg/dynamic.go @@ -1,6 +1,10 @@ package rpg -import "time" +import ( + "time" + + "github.com/aerth/rpc/librpg/common" +) // doors // loot @@ -8,7 +12,7 @@ import "time" type DObjectType int type DObject struct { - Object Object + Object common.Object Contains []Item Until time.Time `json:"-"` Type DObjectType diff --git a/effects.go b/librpg/effects.go similarity index 100% rename from effects.go rename to librpg/effects.go diff --git a/inventory.go b/librpg/inventory.go similarity index 100% rename from inventory.go rename to librpg/inventory.go diff --git a/item_test.go b/librpg/item_test.go similarity index 100% rename from item_test.go rename to librpg/item_test.go diff --git a/items.go b/librpg/items.go similarity index 97% rename from items.go rename to librpg/items.go index 0b2d7c7..549c33c 100644 --- a/items.go +++ b/librpg/items.go @@ -8,6 +8,7 @@ import ( "strings" "time" + "github.com/aerth/rpc/librpg/common" "github.com/faiface/pixel" ) @@ -232,9 +233,9 @@ func (w *World) NewLoot(location pixel.Vec, items []Item) { Contains: items, Type: D_LOOT, Until: time.Now().Add(5 * time.Minute), - Object: Object{ + Object: common.Object{ Loc: location, - Rect: DefaultSpriteRectangle.Moved(location), + Rect: common.DefaultSpriteRectangle.Moved(location), }, } w.DObjects = append(w.DObjects, loot) diff --git a/living.go b/librpg/living.go similarity index 79% rename from living.go rename to librpg/living.go index f17bd3b..6743624 100644 --- a/living.go +++ b/librpg/living.go @@ -9,6 +9,8 @@ import ( "golang.org/x/image/colornames" + "github.com/aerth/rpc/librpg/common" + astar "github.com/beefsack/go-astar" "github.com/faiface/pixel" "github.com/faiface/pixel/imdraw" ) @@ -38,6 +40,74 @@ type Entity struct { imd *imdraw.IMDraw // health bar } +func (e *Entity) pathcalc(target pixel.Vec) { + var ( + maxcost = 1000.00 + ) + if !e.calculated.IsZero() && time.Since(e.calculated) < time.Millisecond { + + return + } + e.calculated = time.Now() + + // get tiles, give world + tile := e.w.Tile(e.Rect.Center()) + tile.W = e.w + targett := e.w.Tile(target) + targett.W = e.w + + // check + if tile.Type == common.O_NONE { + // bad spawn, respawn + e.P.Health = 0 + return + } + if targett.Type == common.O_NONE { + // player must be flying + e.calculated = time.Now().Add(3 * time.Second) + return + } + + est := tile.PathEstimatedCost(targett) + if est < 64 { + //log.Println("direct to char", e, est) + e.paths = []pixel.Vec{e.w.Char.Rect.Center(), e.w.Char.Rect.Center(), e.w.Char.Rect.Center()} + return + } + + if tile.PathEstimatedCost(targett) > 400 { + // too far + //log.Println("path too expensive, trying in 3 seconds") + e.calculated = time.Now().Add(1 * time.Second) + return + } + + // calculate path + path, distance, found := astar.Path(tile, targett) + if found { + if distance > maxcost { // cost path + e.calculated = time.Now().Add(3 * time.Second) + log.Println("too far") + e.paths = nil + return + } + //log.Println("distance:", distance) + var paths []pixel.Vec + + for _, p := range path { + + //log.Println(p) + center := p.(common.Object).Loc.Add(common.DefaultSpriteRectangle.Center()) + paths = append(paths, center) + } + + e.paths = paths + + return + } + //log.Println(e.Name, "no path found, distance:", distance) +} + type EntityType int type EntityState int @@ -164,7 +234,7 @@ func (e *Entity) Draw(t pixel.Target, w *World) { rect.Max.Y = rect.Min.Y + 2 rect.Max.X = rect.Min.X + 30 if e.P.Health > 0 { - DrawBar(e.imd, colornames.Red, e.P.Health, e.P.MaxHealth, rect) + common.DrawBar(e.imd, colornames.Red, e.P.Health, e.P.MaxHealth, rect) e.imd.Draw(t) } /* good debug square @@ -180,7 +250,7 @@ func (e *Entity) Center() pixel.Vec { } func (e *Entity) ChangeMind(dt float64) { - if t := e.w.Tile(e.Center()); t.Type != O_TILE { + if t := e.w.Tile(e.Center()); t.Type != common.O_TILE { e.Phys.Vel = pixel.ZV return } @@ -222,9 +292,9 @@ func (e *Entity) ChangeMind(dt float64) { func (e *Entity) Update(dt float64) { blk := e.w.Tile(e.Rect.Center()) - if blk.Type != O_TILE { + if blk.Type != common.O_TILE { old := e.Rect.Center() - e.Rect = DefaultEntityRectangle.Moved(TileNear(e.w.Tiles, e.Center()).Loc) + e.Rect = DefaultEntityRectangle.Moved(common.TileNear(e.w.Tiles, e.Center()).Loc) log.Println("Moved skel:", old, "to", e.Rect.Center()) return } @@ -248,13 +318,13 @@ func (e *Entity) Update(dt float64) { // move next := e.Rect.Moved(e.Phys.Vel.Scaled(dt)) t := w.Tile(next.Center()) - if t.Type == O_NONE && !e.P.CanFly { + if t.Type == common.O_NONE && !e.P.CanFly { return } - if !e.P.CanFly && t.Type == O_BLOCK { + if !e.P.CanFly && t.Type == common.O_BLOCK { log.Println(e.Type, "got blocked", t.Loc) next = e.Rect.Moved(e.Phys.Vel.Scaled(-dt * 10)) - if w.Tile(next.Center()).Type != O_TILE { + if w.Tile(next.Center()).Type != common.O_TILE { log.Println("returning") return } @@ -266,7 +336,7 @@ func (e *Entity) Update(dt float64) { return true } for _, c := range w.Blocks { - if c.Type == O_BLOCK && c.Rect.Contains(dot) { + if c.Type == common.O_BLOCK && c.Rect.Contains(dot) { return false } } @@ -278,7 +348,7 @@ func (e *Entity) Update(dt float64) { return true } for _, c := range w.Tiles { - if c.Type == O_TILE && c.Rect.Contains(dot) { + if c.Type == common.O_TILE && c.Rect.Contains(dot) { return true } @@ -359,11 +429,11 @@ func (w *World) NewMobs(n int) { npc.P.Health = 2000 npc.P.MaxHealth = 2000 // npc.CanFly = true - npc.Rect = npc.Rect.Moved(FindRandomTile(w.Tiles)) + npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) for i := 1; i < n; i++ { npc = w.NewEntity(SKELETON) - npc.Rect = npc.Rect.Moved(FindRandomTile(w.Tiles)) + npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) } } diff --git a/load.go b/librpg/load.go similarity index 100% rename from load.go rename to librpg/load.go diff --git a/librpg/maps/map.go b/librpg/maps/map.go new file mode 100644 index 0000000..4a32082 --- /dev/null +++ b/librpg/maps/map.go @@ -0,0 +1,151 @@ +package maps + +import ( + "crypto/md5" + "encoding/json" + "io/ioutil" + "log" + "math" + "math/big" + "math/rand" + "time" + + "github.com/aerth/rpc/librpg/common" + "github.com/beefsack/go-astar" + "github.com/faiface/pixel" +) + +var BOUNDS float64 = 700 +var numbers = "0123456789" + +// func MapSeedInit() { +// rand.Seed(time.Now().UnixNano()) +// // seed or random +// if len(os.Args) == 2 { +// if strings.HasPrefix(os.Args[1], "-h") { +// fmt.Println("Usage:") +// fmt.Println("\tmapgen [seed]") +// fmt.Println("Example:") +// fmt.Println("\tmapgen mycoolseed") + +// os.Exit(111) +// } +// hashb := md5.Sum([]byte(os.Args[1])) +// hash := []byte(fmt.Sprintf("%x", hashb)) +// var seed []byte +// for _, b := range hash { +// if bytes.IndexAny([]byte{b}, numbers) != -1 { +// log.Println(string(b), "is a number") +// seed = append(seed, b) +// } else { +// log.Println(string(b), "is a letter") +// } + +// } +// worldseed, err := strconv.ParseInt(string(seed), 10, 64) +// if err != nil { +// log.Println(err) +// } +// rand.Seed(worldseed) +// log.Printf("Using world seed: %q -> %v", os.Args[1], worldseed) +// log.Printf("Hash: %q", string(hash)) +// } +// // create maps dir if not exist +// os.Mkdir("maps", 0755) +// } +func GenerateMap(seed string) []common.Object { + if seed == "" { + rand.Seed(time.Now().UnixMicro()) + } else { + b := md5.Sum([]byte(seed)) + rand.Seed(big.NewInt(0).SetBytes((b[:])).Int64()) + } + var olist []common.Object + t := common.O_TILE + for i := 0; i < 100; i++ { + + currentThing := 20 // grass + t = common.O_TILE + if i%3 == 0 { + currentThing = 53 // water + t = common.O_BLOCK + } + xmin := randfloat() + ymin := randfloat() + xmax := randfloat() + ymax := randfloat() + box := pixel.R(xmin, ymin, xmax, ymax).Norm() + log.Println(t, box) + pattern := common.DrawPatternObject(currentThing, t, box, 100) + for _, obj := range pattern { + if common.GetObjects(olist, obj.Loc) == nil { + olist = append(olist, obj) + } + } + + } + + // make dummy world for path finding + world := &MiniWorld{Tiles: olist} + + // world.Tiles = common.GetTiles(olist) + + // detect islands, make bridges + oldlist := olist + olist = nil + spot := world.Tile(common.FindRandomTile(oldlist)) + for _, o := range oldlist { + o.W = world + _, _, found := astar.Path(o, spot) + if o.Type == common.O_TILE && !found { + log.Println("found island tile", o) + } else { + olist = append(olist, o) + } + } + + // fill in with water blocks + waterworld := common.DrawPatternObject(53, common.O_BLOCK, pixel.R(-BOUNDS, -BOUNDS, BOUNDS, BOUNDS), 0) + for _, water := range waterworld { + if common.GetObjects(olist, water.Loc) == nil { + olist = append(olist, water) + } + } + + return olist +} + +func SaveMap(olist []common.Object) { + + b, err := json.Marshal(olist) + if err != nil { + log.Println(err) + return + } + f, err := ioutil.TempFile("maps", "map") + if err != nil { + log.Println(err) + return + } + _, err = f.Write(b) + if err != nil { + log.Println(err) + return + } + log.Println("saved file:", f.Name()) +} + +func randfloat() float64 { + step := 32.00 + f := float64(rand.Intn(int(BOUNDS))) + f = math.Floor(f) + f = float64(int(f/step)) * step + switch rand.Intn(2) { + case 0: + f = -f + default: + } + + return f + +} diff --git a/librpg/maps/miniworld.go b/librpg/maps/miniworld.go new file mode 100644 index 0000000..52ca041 --- /dev/null +++ b/librpg/maps/miniworld.go @@ -0,0 +1,35 @@ +package maps + +import ( + "log" + + "github.com/aerth/rpc/librpg/common" + "github.com/faiface/pixel" +) + +type MiniWorld struct { + Tiles []common.Object +} + +// Tile scans tiles and returns the first tile located at dot +func (w *MiniWorld) Tile(dot pixel.Vec) common.Object { + if w.Tiles == nil { + log.Println("nil tiles!") + return common.Object{W: w, Type: common.O_BLOCK} + } + + if len(w.Tiles) == 0 { + log.Println("no tiles to look in") + return common.Object{W: w, Type: common.O_BLOCK} + } + for i := len(w.Tiles) - 1; i >= 0; i-- { + if w.Tiles[i].Rect.Contains(dot) { + ob := w.Tiles[i] + ob.W = w + return ob + } + } + // log.Println("no tiles found at location:", dot) + // panic("bug") + return common.Object{W: w, Type: common.O_BLOCK} +} diff --git a/menu.go b/librpg/menu.go similarity index 89% rename from menu.go rename to librpg/menu.go index a027be8..00e3dd1 100644 --- a/menu.go +++ b/librpg/menu.go @@ -8,6 +8,7 @@ import ( "golang.org/x/image/colornames" + "github.com/aerth/rpc/librpg/common" "github.com/faiface/pixel" "github.com/faiface/pixel/imdraw" "github.com/faiface/pixel/pixelgl" @@ -116,8 +117,8 @@ func (c *Character) DrawBars(target pixel.Target, bounds pixel.Rect) { rect := bounds rect.Min.Y = startY rect.Max.Y = rect.Min.Y + barheight - DrawBar(imd, colornames.Red, float64(c.Health), float64(255), rect) - DrawBar(imd, colornames.Blue, float64(c.Mana), 255.00, rect.Moved(pixel.V(0, barheight+1))) - DrawBar(imd, colornames.Purple, xp, next, rect.Moved(pixel.V(0, (barheight*2)+1))) + common.DrawBar(imd, colornames.Red, float64(c.Health), float64(255), rect) + common.DrawBar(imd, colornames.Blue, float64(c.Mana), 255.00, rect.Moved(pixel.V(0, barheight+1))) + common.DrawBar(imd, colornames.Purple, xp, next, rect.Moved(pixel.V(0, (barheight*2)+1))) imd.Draw(target) } diff --git a/mob_test.go b/librpg/mob_test.go similarity index 87% rename from mob_test.go rename to librpg/mob_test.go index a728fba..d002054 100644 --- a/mob_test.go +++ b/librpg/mob_test.go @@ -8,7 +8,7 @@ import ( ) func TestMobCenter(t *testing.T) { - w := NewWorld("1", 1) + w := NewWorld("world name", 1, "1") mob := w.NewEntity(SKELETON) mob.Rect = mob.Rect.Moved(pixel.V(100, 100)) if mob.Center() != pixel.V(100, 100) { diff --git a/names.go b/librpg/names.go similarity index 100% rename from names.go rename to librpg/names.go diff --git a/names_test.go b/librpg/names_test.go similarity index 100% rename from names_test.go rename to librpg/names_test.go diff --git a/librpg/newworld.go b/librpg/newworld.go new file mode 100644 index 0000000..237782b --- /dev/null +++ b/librpg/newworld.go @@ -0,0 +1,114 @@ +package rpg + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + + "golang.org/x/image/colornames" + + "github.com/aerth/rpc/librpg/common" + "github.com/aerth/rpg/assets" + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" +) + +func NewGame(win *pixelgl.Window, difficulty int, leveltest string, worldseed string) { + world := NewWorld(leveltest, difficulty, worldseed) + if world == nil { + log.Println("bad world") + os.Exit(111) + } + // have window, have world + if err := world.drawTiles("tileset.png"); err != nil { + log.Println("bad tiles", err) + os.Exit(111) + } + + if TitleMenu(win) { + os.Exit(0) + } + var camPos = pixel.V(0, 0) + for !win.Closed() { + if win.Pressed(pixelgl.KeyLeft) { + camPos.X-- + log.Println(camPos) + } + if win.Pressed(pixelgl.KeyRight) { + camPos.X++ + log.Println(camPos) + } + if win.Pressed(pixelgl.KeyUp) { + camPos.Y++ + log.Println(camPos) + } + if win.Pressed(pixelgl.KeyDown) { + camPos.Y-- + log.Println(camPos) + } + win.SetMatrix(pixel.IM.Moved(camPos)) + win.Clear(colornames.Green) + world.Draw(win) + win.Update() + } + os.Exit(111) +} + +func (w *World) LoadMapFile(path string) error { + b, err := ioutil.ReadFile(path) + if err != nil { + return err + } + return w.loadmap(b) +} +func (w *World) LoadMap(path string) error { + b, err := assets.Asset(path) + if err != nil { + return err + } + return w.loadmap(b) +} +func (w *World) loadmap(b []byte) error { + var things = []common.Object{} + err := json.Unmarshal(b, &things) + if err != nil { + return fmt.Errorf("invalid map: %v", err) + } + return w.injectMap(things) +} + +func (w *World) InjectMap(things []common.Object) error { + return w.injectMap(things) +} + +func (w *World) injectMap(things []common.Object) error { + total := len(things) + for i, t := range things { + t.W = w + t.Rect = common.DefaultSpriteRectangle.Moved(t.Loc) + switch t.SpriteNum { + case 53: // water + t.Type = common.O_BLOCK + default: + } + + switch t.Type { + case common.O_BLOCK: + //log.Printf("%v/%v block object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) + w.Blocks = append(w.Blocks, t) + case common.O_TILE: + //log.Printf("%v/%v tile object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) + w.Tiles = append(w.Tiles, t) + + default: // + log.Printf("%v/%v skipping bad object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) + } + } + log.Printf("map has %v blocks, %v tiles", len(w.Blocks), len(w.Tiles)) + if len(w.Blocks) == 0 && len(w.Tiles) == 0 { + return fmt.Errorf("invalid map") + } + return nil +} diff --git a/librpg/patterns.go b/librpg/patterns.go new file mode 100644 index 0000000..f8b8679 --- /dev/null +++ b/librpg/patterns.go @@ -0,0 +1,10 @@ +package rpg + +import ( + "math/rand" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} diff --git a/regions.go b/librpg/regions.go similarity index 71% rename from regions.go rename to librpg/regions.go index fc9b889..5623e7e 100644 --- a/regions.go +++ b/librpg/regions.go @@ -1,12 +1,16 @@ package rpg -import "log" +import ( + "log" + + "github.com/aerth/rpc/librpg/common" +) type Region struct { Name string ID int Portals map[string]int // region id - Map []Object + Map []common.Object DObjects []*DObject } @@ -17,7 +21,7 @@ func (r Region) String() string { return "region " + r.Name } -func (w *World) NewRegion(name string, mapobjects []Object) *Region { +func (w *World) NewRegion(name string, mapobjects []common.Object) *Region { r := new(Region) r.ID = len(w.Regions) r.Map = mapobjects diff --git a/spells.go b/librpg/spells.go similarity index 100% rename from spells.go rename to librpg/spells.go diff --git a/stats.go b/librpg/stats.go similarity index 100% rename from stats.go rename to librpg/stats.go diff --git a/strings.go b/librpg/strings.go similarity index 86% rename from strings.go rename to librpg/strings.go index 6404695..a8c2aac 100644 --- a/strings.go +++ b/librpg/strings.go @@ -38,16 +38,6 @@ func (i ItemType) String() string { return _ItemType_name[_ItemType_index[i]:_ItemType_index[i+1]] } -const _ObjectType_name = "O_NONEO_TILEO_BLOCKO_INVISIBLEO_SPECIALO_WINO_DYNAMIC" - -var _ObjectType_index = [...]uint8{0, 6, 12, 19, 30, 39, 44, 53} - -func (i ObjectType) String() string { - if i < 0 || i >= ObjectType(len(_ObjectType_index)-1) { - return fmt.Sprintf("ObjectType(%d)", i) - } - return _ObjectType_name[_ObjectType_index[i]:_ObjectType_index[i+1]] -} const _animState_name = "IdleRunning" diff --git a/text.go b/librpg/text.go similarity index 100% rename from text.go rename to librpg/text.go diff --git a/title.go b/librpg/title.go similarity index 100% rename from title.go rename to librpg/title.go diff --git a/util.go b/librpg/util.go similarity index 100% rename from util.go rename to librpg/util.go diff --git a/world.go b/librpg/world.go similarity index 79% rename from world.go rename to librpg/world.go index 0d7ba81..97194dd 100644 --- a/world.go +++ b/librpg/world.go @@ -8,6 +8,8 @@ import ( "golang.org/x/image/colornames" + "github.com/aerth/rpc/librpg/common" + "github.com/aerth/rpc/librpg/maps" "github.com/faiface/pixel" "github.com/faiface/pixel/imdraw" "github.com/faiface/pixel/text" @@ -21,7 +23,7 @@ type World struct { Bounds pixel.Rect Regions []*Region DObjects []*DObject - Tiles, Blocks []Object // sorted + Tiles, Blocks []common.Object // sorted Background string // path to pic, will be repeated xy if not empty background *pixel.Sprite // sprite to repeat xy Batches map[EntityType]*pixel.Batch // one batch for every spritemap @@ -41,9 +43,15 @@ type WorldSettings struct { NumEnemy int } -func NewWorld(name string, difficulty int) *World { +func NewWorld(name string, difficulty int, seed string) *World { w := new(World) w.Name = name + if name == "" && seed == "" { + seed = fmt.Sprintf("%d", rand.Int63()) + } + if name == "" { + w.Name = fmt.Sprintf("world seed %d", seed) + } w.Color = RandomColor() w.Sheets = make(map[EntityType]pixel.Picture) w.Anims = make(map[EntityType]map[EntityState]map[Direction][]pixel.Rect) @@ -65,11 +73,20 @@ func NewWorld(name string, difficulty int) *World { w.Batches[t] = pixel.NewBatch(&pixel.TrianglesData{}, w.Sheets[t]) } - log.Println("Loading...") - if e := w.LoadMap("maps/" + name + ".map"); e != nil { - log.Println(e) - if e = w.LoadMapFile(name); e != nil { + if name != "" { + log.Println("Loading map", name) + if e := w.LoadMap("maps/" + name + ".map"); e != nil { log.Println(e) + if e = w.LoadMapFile(name); e != nil { + log.Println(e) + return nil + } + } + } else { + log.Println("generating map with seed:", seed) + omap := maps.GenerateMap(seed) + if e := w.injectMap(omap); e != nil { + log.Println("injekcting mapL:", e) return nil } } @@ -77,7 +94,7 @@ func NewWorld(name string, difficulty int) *World { log.Println("Invalid map. No objects found") return nil } - char.Rect = char.Rect.Moved(FindRandomTile(w.Tiles)) + char.Rect = char.Rect.Moved(common.FindRandomTile(w.Tiles)) w.Settings.NumEnemy = difficulty return w } @@ -117,10 +134,10 @@ func (w *World) Update(dt float64) { continue } npc := w.NewEntity(SKELETON_GUARD) - npc.Rect = npc.Rect.Moved(FindRandomTile(w.Tiles)) + npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) entities = append(entities, npc) npc = w.NewEntity(SKELETON) - npc.Rect = npc.Rect.Moved(FindRandomTile(w.Tiles)) + npc.Rect = npc.Rect.Moved(common.FindRandomTile(w.Tiles)) entities = append(entities, npc) } w.Entities = entities @@ -191,15 +208,15 @@ func (w *World) DrawEntity(n int) { } // Tile scans tiles and returns the first tile located at dot -func (w *World) Tile(dot pixel.Vec) Object { +func (w *World) Tile(dot pixel.Vec) common.Object { if w.Tiles == nil { log.Println("nil tiles!") - return Object{W: w, Type: O_BLOCK} + return common.Object{W: w, Type: common.O_BLOCK} } if len(w.Tiles) == 0 { log.Println("no tiles to look in") - return Object{W: w, Type: O_BLOCK} + return common.Object{W: w, Type: common.O_BLOCK} } for i := len(w.Tiles) - 1; i >= 0; i-- { if w.Tiles[i].Rect.Contains(dot) { @@ -210,21 +227,21 @@ func (w *World) Tile(dot pixel.Vec) Object { } // log.Println("no tiles found at location:", dot) // panic("bug") - return Object{W: w, Type: O_BLOCK} + return common.Object{W: w, Type: common.O_BLOCK} } // Block scans blocks and returns the first block located at dot -func (w *World) Block(dot pixel.Vec) Object { +func (w *World) Block(dot pixel.Vec) common.Object { for i := range w.Blocks { if w.Blocks[i].Rect.Contains(dot) { return w.Blocks[i] } } - return Object{} + return common.Object{} } // Object at location -func (w *World) Object(dot pixel.Vec) Object { +func (w *World) Object(dot pixel.Vec) common.Object { if w.Blocks != nil { for _, v := range w.Blocks { if v.Rect.Contains(dot) { @@ -237,7 +254,7 @@ func (w *World) Object(dot pixel.Vec) Object { return v } } - return Object{Type: O_NONE} + return common.Object{Type: common.O_NONE} } @@ -291,7 +308,7 @@ func (w *World) ShowAnimations(imd *imdraw.IMDraw) { func (w *World) HighlightPaths(target pixel.Target) { imd := imdraw.New(nil) for i := range w.Entities { - color := TransparentRed + color := common.TransparentRed if len(w.Entities[i].paths) != 0 { for _, vv := range w.Entities[i].paths { //color = color.Scaled(0.3) @@ -334,7 +351,7 @@ func (w *World) Reset() { w.Char.Level = 0 w.Char.Mana = 255 w.Char.Inventory = []Item{createLoot()} - w.Char.Rect = DefaultPhys.Rect.Moved(FindRandomTile(w.Tiles)) + w.Char.Rect = DefaultPhys.Rect.Moved(common.FindRandomTile(w.Tiles)) w.Char.Phys.Vel = pixel.ZV w.Entities = nil w.NewMobs(w.Settings.NumEnemy) @@ -372,3 +389,23 @@ func (w *World) Init() *pixel.Batch { } return globebatch } + +func (w *World) drawTiles(path string) error { + spritesheet, spritemap := LoadSpriteSheet(path) + // layers (TODO: slice?) + // batch sprite drawing + globebatch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) + // water world 67 wood, 114 117 182 special, 121 135 dirt, 128 blank, 20 grass + // rpg.DrawPattern(batch, spritemap[53], pixel.R(-3000, -3000, 3000, 3000), 100) + + globebatch.Clear() + // draw it on to canvasglobe + for _, o := range w.Tiles { + o.Draw(globebatch, spritesheet, spritemap, 0) + } + for _, o := range w.Blocks { + o.Draw(globebatch, spritesheet, spritemap, 0) + } + w.Batches[EntityType(-1)] = globebatch + return nil +} diff --git a/makefile b/makefile index 827951e..1cb7aa8 100644 --- a/makefile +++ b/makefile @@ -11,7 +11,8 @@ help: dev: clean generate embed-assets build generate: - stringer -output strings.go -type EntityType,EntityState,ItemType,ObjectType,animState,ActionType,StatusEffect,DObjectType + stringer -output librpg/common/strings.go -type EntityType,EntityState,ItemType,ObjectType,animState,ActionType,StatusEffect,DObjectType librpg/common + embed-assets: @test -x ${shell which go-bindata} && \ @@ -28,14 +29,7 @@ fail: @printf 'build failed. please install dependencies and try again.\nlibgl1-mesa-dev and xorg-dev' clean: - echo cleaning - test -x ${shell which gofmt} && gofmt -w -l -s . || true - rm *_strings.go || true - rm landmap || true - rm pdata || true - rm aerpg || true - rm mapmaker || true - echo "now run 'make'" + rm -rf ./bin key: test -f rpg.key || ssh-keygen -f rpg.key diff --git a/objects.go b/objects.go deleted file mode 100644 index 2d5154d..0000000 --- a/objects.go +++ /dev/null @@ -1,351 +0,0 @@ -package rpg - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "log" - "math/rand" - "time" - - "golang.org/x/image/colornames" - - "github.com/aerth/rpg/assets" - "github.com/faiface/pixel" - "github.com/faiface/pixel/imdraw" -) - -var DefaultSpriteRectangle = pixel.R(-16, -16, 16, 16) - -type ObjectType int - -const ( - O_NONE ObjectType = iota - O_TILE - O_BLOCK - O_INVISIBLE - O_SPECIAL - O_WIN - O_DYNAMIC // loot, doors -) - -//var DefaultSpriteRectangle = pixel.R(-16, 0, 16, 32) -//var DefaultSpriteRectangle = pixel.R(-16, 0, 16, 32) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -type Object struct { - Loc pixel.Vec `json:"L"` - Rect pixel.Rect `json:"-"` - Type ObjectType `json:"T,ObjectType"` - P ObjectProperties `json:",omitempty"` - SpriteNum int `json:"S,omitempty"` - Sprite *pixel.Sprite `json:"-"` - W *World `json:"-"` - // Contains []Item `json:"-"` -} - -func (o Object) String() string { - return fmt.Sprintf("%s %s %s %v", o.Loc, o.Rect, o.Type, o.SpriteNum) -} - -type ObjectProperties struct { - Invisible bool `json:",omitempty"` - // Tile bool `json:",omitempty"` - // Block bool `json:",omitempty"` - Special bool `json:",omitempty"` -} - -func NewTile(loc pixel.Vec) Object { - return Object{ - Loc: loc, - Rect: pixel.Rect{loc.Sub(pixel.V(16, 16)), loc.Add(pixel.V(16, 16))}, - Type: O_TILE, - } -} -func NewBlock(loc pixel.Vec) Object { - return Object{ - Loc: loc, - Rect: pixel.Rect{loc.Sub(pixel.V(16, 16)), loc.Add(pixel.V(16, 16))}, - Type: O_BLOCK, - } -} - -func NewTileBox(rect pixel.Rect) Object { - return Object{ - Rect: rect, - Type: O_TILE, - } -} -func NewBlockBox(rect pixel.Rect) Object { - return Object{ - Rect: rect, - Type: O_BLOCK, - } -} - -var TransparentBlue = pixel.ToRGBA(colornames.Blue).Scaled(0.4) -var TransparentRed = pixel.ToRGBA(colornames.Red).Scaled(0.4) -var TransparentPurple = pixel.ToRGBA(colornames.Purple).Scaled(0.4) - -func (o Object) Highlight(win pixel.Target, color pixel.RGBA) { - imd := imdraw.New(nil) - imd.Color = color - imd.Push(o.Rect.Min, o.Rect.Max) - imd.Rectangle(1) - imd.Draw(win) -} -func (o Object) Draw(win pixel.Target, spritesheet pixel.Picture, sheetFrames []*pixel.Sprite, scaled float64) { - // r := pixel.Rect{o.Loc, o.w.Char.Rect.Center()} - // sz := r.Size() - // if sz.X > 1000 || sz.Y > 1000 { - // return - // } - if o.P.Invisible { - return - } - if o.Sprite == nil && o.Type != O_DYNAMIC { - if 0 > o.SpriteNum || o.SpriteNum > len(sheetFrames) { - log.Printf("unloadable sprite: %v/%v", o.SpriteNum, len(sheetFrames)) - return - } - o.Sprite = sheetFrames[o.SpriteNum] - } - if o.Sprite == nil && o.Type == O_DYNAMIC { - o.Sprite = sheetFrames[0] - } - // if o.Loc == pixel.ZV && o.Rect.Size().Y != 32 { - // log.Println(o.Rect.Size(), "cool rectangle", o.SpriteNum) - // DrawPattern(win, o.Sprite, o.Rect, 0) - // } else { - if scaled != 0.00 { - o.Sprite.Draw(win, pixel.IM.Scaled(pixel.ZV, scaled).Moved(o.Loc)) - return - } - - o.Sprite.Draw(win, pixel.IM.Moved(o.Loc)) - // } - -} -func (w *World) LoadMapFile(path string) error { - b, err := ioutil.ReadFile(path) - if err != nil { - return err - } - return w.loadmap(b) -} -func (w *World) LoadMap(path string) error { - b, err := assets.Asset(path) - if err != nil { - return err - } - return w.loadmap(b) -} -func (w *World) loadmap(b []byte) error { - var things = []Object{} - err := json.Unmarshal(b, &things) - if err != nil { - return fmt.Errorf("invalid map: %v", err) - } - total := len(things) - for i, t := range things { - t.W = w - t.Rect = DefaultSpriteRectangle.Moved(t.Loc) - switch t.SpriteNum { - case 53: // water - t.Type = O_BLOCK - default: - } - - switch t.Type { - case O_BLOCK: - //log.Printf("%v/%v block object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) - w.Blocks = append(w.Blocks, t) - case O_TILE: - //log.Printf("%v/%v tile object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) - w.Tiles = append(w.Tiles, t) - - default: // - log.Printf("%v/%v skipping bad object: %s %v %s", i, total, t.Loc, t.SpriteNum, t.Type) - } - } - log.Printf("map has %v blocks, %v tiles", len(w.Blocks), len(w.Tiles)) - if len(w.Blocks) == 0 && len(w.Tiles) == 0 { - return fmt.Errorf("invalid map") - } - return nil -} - -// assumes only tiles are given -func FindRandomTile(os []Object) pixel.Vec { - if len(os) == 0 { - panic("no objects") - } - ob := os[rand.Intn(len(os))] - if ob.Loc != pixel.ZV && ob.SpriteNum != 0 && ob.Type == O_TILE { - return ob.Rect.Center() - } - return FindRandomTile(os) -} - -func GetObjects(objects []Object, position pixel.Vec) []Object { - var good []Object - for _, o := range objects { - if o.Rect.Contains(position) { - good = append(good, o) - } - } - return good -} - -func GetTiles(objects []Object) []Object { - var tiles []Object - for _, o := range objects { - if o.Type == O_TILE { - tiles = append(tiles, o) - } - } - return tiles -} - -func TilesAt(objects []Object, position pixel.Vec) []Object { - var good []Object - all := GetObjects(objects, position) - if len(all) > 0 { - for _, o := range all { - if DefaultSpriteRectangle.Moved(o.Loc).Contains(position) && o.Type == O_TILE { - good = append(good, o) - } - - } - } - return good - -} -func GetObjectsAt(objects []Object, position pixel.Vec) []Object { - var good []Object - all := GetObjects(objects, position) - if len(all) > 0 { - for _, o := range all { - if DefaultSpriteRectangle.Moved(o.Loc).Contains(position) { - good = append(good, o) - } - - } - } - return good - -} - -func GetBlocks(objects []Object, position pixel.Vec) []Object { - var bad []Object - all := GetObjects(objects, position) - if len(all) > 0 { - for _, o := range all { - if o.Type == O_BLOCK { - bad = append(bad, o) - } - - } - } - return bad -} - -// GetNeighbors gets the neighboring tiles -func (o Object) GetNeighbors() []Object { - neighbors := []Object{} - of := 32.0 - for _, offset := range [][]float64{ - {-of, 0}, - {of, 0}, - {0, -of}, - {0, of}, - } { - if n := o.W.Tile(pixel.V(o.Rect.Center().X+offset[0], o.Rect.Center().Y+offset[1])); n.Type == o.Type { - neighbors = append(neighbors, n) - } - } - return neighbors - -} -func (w *World) drawTiles(path string) error { - spritesheet, spritemap := LoadSpriteSheet(path) - // layers (TODO: slice?) - // batch sprite drawing - globebatch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet) - // water world 67 wood, 114 117 182 special, 121 135 dirt, 128 blank, 20 grass - // rpg.DrawPattern(batch, spritemap[53], pixel.R(-3000, -3000, 3000, 3000), 100) - - globebatch.Clear() - // draw it on to canvasglobe - for _, o := range w.Tiles { - o.Draw(globebatch, spritesheet, spritemap, 0) - } - for _, o := range w.Blocks { - o.Draw(globebatch, spritesheet, spritemap, 0) - } - w.Batches[EntityType(-1)] = globebatch - return nil -} - -func TileNear(all []Object, loc pixel.Vec) Object { - tile := TilesAt(all, loc) - snap := 32.00 - loc.X = float64(int(loc.X/snap)) * snap - loc.Y = float64(int(loc.Y/snap)) * snap - radius := 1.00 - oloc := loc - if len(tile) > 0 { - oloc = tile[0].Loc - } - log.Println("looking for loc:", loc) - for i := 0; i < len(all); i++ { - loc.X += radius * 16 - loc.Y += radius * 16 - if loc == oloc { - continue - } - log.Println("Checking loc", loc) - os := TilesAt(all, loc) - if len(os) > 0 { - if os[0].Loc == pixel.ZV || os[0].Loc == oloc { - continue - } - return os[0] - } - loc2 := loc - loc2.X = -loc.X - os = TilesAt(all, loc.Scaled(-1)) - if len(os) > 0 { - if os[0].Loc == pixel.ZV || os[0].Loc == oloc { - continue - } - return os[0] - } - os = TilesAt(all, loc2.Scaled(1)) - if len(os) > 0 { - if os[0].Loc == pixel.ZV || os[0].Loc == oloc { - continue - } - return os[0] - } - - os = TilesAt(all, loc2.Scaled(-1)) - if len(os) > 0 { - if os[0].Loc == pixel.ZV || os[0].Loc == oloc { - continue - } - return os[0] - } - - if i%4 == 0 { - radius++ - loc.X += 16 - } - } - log.Println("not found") - return Object{Type: O_NONE} - -} diff --git a/path.go b/path.go deleted file mode 100644 index 7723d70..0000000 --- a/path.go +++ /dev/null @@ -1,132 +0,0 @@ -package rpg - -import ( - "log" - "time" - - astar "github.com/beefsack/go-astar" - "github.com/faiface/pixel" -) - -func (e *Entity) pathcalc(target pixel.Vec) { - var ( - maxcost = 1000.00 - ) - if !e.calculated.IsZero() && time.Since(e.calculated) < time.Millisecond { - - return - } - e.calculated = time.Now() - - // get tiles, give world - tile := e.w.Tile(e.Rect.Center()) - tile.W = e.w - targett := e.w.Tile(target) - targett.W = e.w - - // check - if tile.Type == O_NONE { - // bad spawn, respawn - e.P.Health = 0 - return - } - if targett.Type == O_NONE { - // player must be flying - e.calculated = time.Now().Add(3 * time.Second) - return - } - - est := tile.PathEstimatedCost(targett) - if est < 64 { - //log.Println("direct to char", e, est) - e.paths = []pixel.Vec{e.w.Char.Rect.Center(), e.w.Char.Rect.Center(), e.w.Char.Rect.Center()} - return - } - - if tile.PathEstimatedCost(targett) > 400 { - // too far - //log.Println("path too expensive, trying in 3 seconds") - e.calculated = time.Now().Add(1 * time.Second) - return - } - - // calculate path - path, distance, found := astar.Path(tile, targett) - if found { - if distance > maxcost { // cost path - e.calculated = time.Now().Add(3 * time.Second) - log.Println("too far") - e.paths = nil - return - } - //log.Println("distance:", distance) - var paths []pixel.Vec - - for _, p := range path { - - //log.Println(p) - center := p.(Object).Loc.Add(DefaultSpriteRectangle.Center()) - paths = append(paths, center) - } - - e.paths = paths - - return - } - //log.Println(e.Name, "no path found, distance:", distance) -} - -func (o Object) PathNeighbors() []astar.Pather { - neighbors := []astar.Pather{} - of := 32.0 - //of = 24.0 - - for _, offset := range [][]float64{ - {-of, 0}, - {of, 0}, - {0, -of}, - {0, of}, - //{of, -of}, - //{-of, -of}, - //{of, of}, - //{-of, of}, - } { - n := o.W.Tile(pixel.V(o.Rect.Center().X+offset[0], o.Rect.Center().Y+offset[1])) - if n.Type == O_TILE { - neighbors = append(neighbors, n) - } - } - return neighbors -} - -func (o Object) PathEstimatedCost(to astar.Pather) float64 { - toT := to.(Object) - absX := toT.Rect.Center().X - o.Rect.Center().X - if absX < 0 { - absX = -absX - } - absY := toT.Rect.Center().Y - o.Rect.Center().Y - if absY < 0 { - absY = -absY - } - return float64(absX + absY) - -} - -func (o Object) PathNeighborCost(to astar.Pather) float64 { - toT := to.(Object) - cost := tileCosts[toT.Type] - return cost -} - -// KindCosts map tile kinds to movement costs. - -var tileCosts = map[ObjectType]float64{ - O_NONE: 30.00, - O_BLOCK: 30.00, - O_INVISIBLE: 3.00, - O_SPECIAL: 0.00, - O_TILE: 1.00, - O_WIN: 0.00, - O_DYNAMIC: 3.00, -} diff --git a/sequence.go b/sequence.go deleted file mode 100644 index 012b8b3..0000000 --- a/sequence.go +++ /dev/null @@ -1,61 +0,0 @@ -package rpg - -import ( - "log" - "os" - - "golang.org/x/image/colornames" - - "github.com/faiface/pixel" - "github.com/faiface/pixel/pixelgl" -) - -func NewGame(win *pixelgl.Window, difficulty int, leveltest ...string) { - if leveltest == nil || len(leveltest) == 0 { - leveltest = []string{""} - } - var ( - level = "1" - ) - if leveltest[0] != "" { - level = leveltest[0] - } - world := NewWorld(level, difficulty) - if world == nil { - log.Println("bad world") - os.Exit(111) - } - // have window, have world - if err := world.drawTiles("tileset.png"); err != nil { - log.Println("bad tiles", err) - os.Exit(111) - } - - if TitleMenu(win) { - os.Exit(0) - } - var camPos = pixel.V(0, 0) - for !win.Closed() { - if win.Pressed(pixelgl.KeyLeft) { - camPos.X-- - log.Println(camPos) - } - if win.Pressed(pixelgl.KeyRight) { - camPos.X++ - log.Println(camPos) - } - if win.Pressed(pixelgl.KeyUp) { - camPos.Y++ - log.Println(camPos) - } - if win.Pressed(pixelgl.KeyDown) { - camPos.Y-- - log.Println(camPos) - } - win.SetMatrix(pixel.IM.Moved(camPos)) - win.Clear(colornames.Green) - world.Draw(win) - win.Update() - } - os.Exit(111) -}