package tile import "gitlab.com/beoran/ebsgo/monolog" import "fmt" import "strings" import "bytes" import "strconv" import "io/ioutil" import "compress/gzip" import "compress/zlib" import "encoding/binary" import "encoding/base64" import "gitlab.com/beoran/al5go/al" // import "gitlab.com/beoran/ebsgo/engine/geometry/point" // import "gitlab.com/beoran/ebsgo/engine/physics/camera" import "gitlab.com/beoran/ebsgo/engine/fifi" import "github.com/beevik/etree" // Tile flipping constants const ( TMX_FLIPPED_HORIZONTALLY = 0x80000000 TMX_FLIPPED_VERTICALLY = 0x40000000 TMX_FLIPPED_DIAGONALLY = 0x20000000 TMX_FLIPPED_FILTER = (^uint32(0xE0000000)) ) func AttrInt(e * etree.Element, key string) (int, error) { return strconv.Atoi(e.SelectAttrValue(key, "")) } func AttrFloat64(e * etree.Element, key string) (float64, error) { return strconv.ParseFloat(e.SelectAttrValue(key, ""), 64) } func Property(e * etree.Element, propname string) (string, bool) { properties := e.FindElements("//properties/property") for _, property := range properties { name := property.SelectAttrValue("name", "") if name == propname { return property.SelectAttrValue("value", ""), true } } return "", false } func PropertyInt(e * etree.Element, propname string) (int, error) { value , ok := Property(e, propname) if (!ok) { return 0, fmt.Errorf("no such property") } return strconv.Atoi(value) } func LoadFrame(xml_frame * etree.Element, tile * Tile, set * Set) * Tile { tile_id, err := AttrInt(xml_frame, "tileid") if err != nil { return tile; } // the tile id obtained here is correct and doesn't need to be // lessened by firstgid duration, err := AttrFloat64(xml_frame, "duration") if err != nil { return tile; } /* Convert ms to s. */ duration /= 1000.0 frame := tile.AddAnimationFrame(tile_id, duration) monolog.Log("TILEIO", "Frame loaded: %d %f", frame.Index, frame.Duration) return tile } func LoadAnimation(xml_tile * etree.Element, tile * Tile, set * Set) * Tile { xml_anim := xml_tile.FindElement("//animation") if xml_anim == nil { return tile; } xml_frames := xml_anim.FindElements("//frame") for _, xml_frame := range xml_frames { LoadFrame(xml_frame, tile, set) } monolog.Log("TILEIO", "Loaded animation: %d", len(tile.Frames)) return tile } func XmlToTileType(xml_tile * etree.Element) (uint, error) { value , ok := Property(xml_tile, "type") if (!ok) { return 0, fmt.Errorf("no type property") } result, ok := FlagNames[value] if ! ok { return 0, fmt.Errorf("unknown type property") } return result, nil } func LoadTile(xml_tile * etree.Element, index int, set * Set, tm * Map) (*Tile, error) { id, err := AttrInt(xml_tile, "id") if err != nil { return nil, fmt.Errorf("Tile id not found: %s", err) } tile := set.Tile(id) monolog.Log("TILEIO", "Got tile: %d <- %d", tile.Active, tile.Index) if tile == nil { return nil, fmt.Errorf("Tile id not found in tile set: %d", id) } value , ok := Property(xml_tile, "type") if (ok) { parts := strings.Split(value, ",") for _, part := range parts { clean := strings.Trim(part, "") tile.SetProperty(clean) } } // ianim , err := PropertyInt(xml_tile, "anim" ) // iwait , err := PropertyInt(xml_tile, "wait" ) ilight , err := PropertyInt(xml_tile, "light" ) iblend , err := PropertyInt(xml_tile, "blend" ) ishadow , err := PropertyInt(xml_tile, "shadow" ) ilightmask , err := PropertyInt(xml_tile, "lightmask" ) iblendmask , err := PropertyInt(xml_tile, "blendmask" ) ishadowmask , err := PropertyInt(xml_tile, "shadowmask" ) // No support anymore for "classic" animations since TMX now // supports animations. tile.Light = ilight tile.Blend = iblend tile.Shadow = ishadow tile.LightMask = ilightmask tile.BlendMask = iblendmask tile.ShadowMask = ishadowmask /* Support tiled-style animations. */ LoadAnimation(xml_tile, tile, set) monolog.Log("TILEIO", "Loaded tile: (%d x %d)", tile.SheetX(tile.Tileset), tile.SheetY(tile.Tileset)) return tile, nil } /** Calculates index and the Allegro draw flags and rotation for a tile. */ func TmxTileIndexFlagsAndRotation(tmx_index uint32) (int, int, float32) { index := int(tmx_index & TMX_FLIPPED_FILTER) flags := int(0) rotate := float32(0.0) if (tmx_index & TMX_FLIPPED_HORIZONTALLY) != 0 { flags |= al.FLIP_HORIZONTAL } if (tmx_index & TMX_FLIPPED_VERTICALLY) != 0 { flags |= al.FLIP_VERTICAL } if (tmx_index & TMX_FLIPPED_DIAGONALLY) != 0 { rotate = float32(al.PI / 2.0) } return index, flags, rotate } func LoadSet(xml_set * etree.Element, index int, tm * Map) (* Set, error) { firstgid, err := AttrInt(xml_set, "firstgid") if err != nil { return nil, fmt.Errorf("Could not find firstgid for tile set %d: %s", index, err) } xml_image := xml_set.FindElement("//image") if xml_image == nil { return nil, fmt.Errorf("Could not find image data for tile set %d", index) } tile_w, err := AttrInt(xml_set, "tilewidth") tile_h, err := AttrInt(xml_set, "tilewidth") if (tile_w < 1) || (tile_h < 1) { return nil, fmt.Errorf("Tile size too small for tile set %d: %d x %d", index, tile_w, tile_h) } iname := xml_image.SelectAttrValue("source", "tile_set_default.png") bmp := fifi.LoadBitmap("map/" + iname) if bmp == nil { return nil, fmt.Errorf("Could not load tile set sheet: %s", iname) } set := NewSet(bmp, tile_w, tile_h, firstgid) monolog.Log("TILEIO", "Loaded tile set: (%d x %d) (%d x %d) %d", set.TileW, set.TileH, set.W, set.H, set.FirstGID) xml_tiles := xml_set.FindElements("//tile") for tile_index, xml_tile := range xml_tiles { LoadTile(xml_tile, tile_index, set, tm) } set.Recalculate() return set, nil } type Expander func(in []byte) ([]byte, error) type Decoder func(data string, w, h int, expand Expander) ([][]uint32, error) func CsvDecoder(data string, w, h int, expand Expander) ([][]uint32, error) { lines := strings.Split(data, "\n") x := 0 y := 0 result := make([][]uint32, h) for _, line := range lines { if line != "" { cells := strings.Split(line, ",") result[y] = make([]uint32, w) for x = 0 ; x < w ; x++ { value := uint32(0) if ( x >= len(cells) ) { break; } res, err := strconv.ParseUint(cells[x], 10, 32) if err == nil { value = uint32(res) } result[y][x] = value } y++ } if y >= h { break; } } return result, nil } func GzipExpander(in []byte) ([]byte, error) { zr, err := gzip.NewReader(bytes.NewReader(in)) if err != nil { return nil, err } return ioutil.ReadAll(zr) } func ZlibExpander(in []byte) ([]byte, error) { zr, err := zlib.NewReader(bytes.NewReader(in)) if err != nil { return nil, err } return ioutil.ReadAll(zr) } func Base64Decoder(data string, w, h int, expand Expander) ([][]uint32, error) { bytes, err := base64.StdEncoding.DecodeString(data) if err != nil { return nil, err } if expand != nil { bytes, err = expand(bytes) if err != nil { return nil, err } } result := make([][]uint32, h) for i := 0 ; i < h; i++ { result[i] = make([]uint32, w) for j := 0 ; i < w ; j++ { index := (i * w + j) * 4 value := binary.LittleEndian.Uint32(bytes[index:4]) result[i][j] = value } } return result, nil } var Decoders map[string]Decoder = map[string]Decoder { "csv" : CsvDecoder, "base64": Base64Decoder, } var Expanders map[string]Expander = map[string]Expander { "gzip" : GzipExpander, "zlib": ZlibExpander, } // Loads a single tile pane of the tile map from xml (tmx). func LoadPane(xml_pane * etree.Element, index int, tm * Map) (* Pane, error) { width, err := AttrInt(xml_pane, "width") height, err := AttrInt(xml_pane, "height") if (width < 1) || (height < 1) { return nil, fmt.Errorf("Layer %i size too small: %d x %d, ", index, width, height) } name := xml_pane.SelectAttrValue("name", "") iset, err := PropertyInt(xml_pane, "tileset") if err != nil { iset = 0 } set := tm.Set(iset) if set == nil { return nil, fmt.Errorf("No tile set found for layer %d: %d", index, iset) } pane := NewPane(set, width, height, name) xml_data := xml_pane.FindElement("//data") if xml_data == nil { return nil, fmt.Errorf("Cannot find layer data for layer %d", index) } encoding := xml_data.SelectAttrValue("encoding" , "csv") compression := xml_data.SelectAttrValue("compression", "") decoder := Decoders[encoding] if decoder == nil { return nil, fmt.Errorf("Unknown encoding %s", encoding) } expander := Expanders[compression] grid, err := decoder(xml_data.Text(), width, height, expander) if err != nil { return nil, fmt.Errorf("Could not decode data for layer %d: %s %s: %s;\nData:>%s<", index, encoding, compression, err, xml_data.Text()) } monolog.Log("TILEIO", "Loaded grid size: (%d x %d) vs size (%d x %d)\n",len(grid), len(grid[0]), height, width) for y := 0 ; y < height ; y ++ { row := grid[y] for x := 0 ; x < width && x < len(row) ; x++ { tmx_index := row[x] if tmx_index == 0 { pane.SetTile(x, y, nil) } else { index, flags, rotation := TmxTileIndexFlagsAndRotation(tmx_index) if pane.Tileset == nil { pane.Tileset = tm.LookupTmxTileset(index) } ebs_index := index - pane.FirstGID() // + 1 cell := pane.Cell(x, y) cell.Tile = pane.Tileset.Tile(ebs_index) cell.Flags = flags cell.Rotate = rotation } } } return pane, nil } // Load a TMX map file from the given XML document. func LoadMapXml(doc * etree.Document) (* Map, error) { root := doc.Root() if root.Tag != "map" { return nil, fmt.Errorf("File is not a TMX map file: %s", root.Tag) } wide, err := AttrInt(root, "width") if err != nil { return nil, fmt.Errorf("Width not a number: %s", err) } high, err := AttrInt(root, "height"); if err != nil { return nil, fmt.Errorf("Height not a number: %s", err) } if (wide < 1) || (high < 1) { return nil, fmt.Errorf("Map size too smalll: %d x %d",wide, high) } tm := NewMap(wide, high) xml_sets := root.FindElements("//tileset") for i, xml_set := range xml_sets { set, err := LoadSet(xml_set, i, tm) if err != nil { return nil, fmt.Errorf("Could not load tile set: %s", err) } else { _ = tm.AddSet(set) } } xml_panes := root.FindElements("//layer") for i, xml_pane := range xml_panes { pane, err := LoadPane(xml_pane, i, tm) if err != nil { return nil, fmt.Errorf("Could not load tile layer: %s", err) } else { tm.AddPane(pane) } } return tm, nil; } // Loads a TMX tile map from the named file. // Return nil on error. func LoadMap(filename string) (* Map, error) { doc := etree.NewDocument() err := doc.ReadFromFile(filename) if err != nil { return nil, err } return LoadMapXml(doc) } func LoadMapFifi(relname string) (* Map, error) { realname := fifi.Map(relname) return LoadMap(realname) }