package tile import "fmt" import "strconv" import "io/ioutil" import "compress/gzip" import "compress/zlib" import "encoding/csv" import "encoding/binary" 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 = (~0xE0000000) ) func AttrInt(e * etree.Element, string key) (int, error) { return strconv.Atoi(e.SelectAttrValue(key, "")) } func AttrFloat64(e * etree.Element, string key) (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 := e.SelectAttrValue("name", "") if name == propname { return e.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(e.SelectAttrValue(key, "")) } 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 tile.AddAnimationFrame(tile_id, 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) } return tile } func XmlToTileType(xml_tile * etree.Element) int, error { value , ok := Property(e, "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) { if id, err := AttrInt(xml_tile, "id") ; err != nil { return nil, fmr.Errorf("Tile id not found: %s", err) } tile := set.Tile(id) if tile == nil { return nil, fmr.Errorf("Tile id not found in tile set: %d", id) } if tiletype, err := XmlToTileType(xml_tile) ; err == nil { tile.Type = tiletype } // 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. */ return LoadAnimation(xml_tile, tile, set) } /** Calculates index and the Allegro draw flags and rotation for a tile. */ func TmxTileIndexFlagsAndRotation(tmx_index int32) int, int, float32 { index := int(tmx_index & TMX_FLIPPED_FILTER) flags := 0 rotate := 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) { rotate := 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("tilewidth") tile_h, err := AttrInt("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) set := NewSet(bmp, tile_w, tile_h, firstgid) xml_tiles := xml_set.FindElements("//tile") for xml_tile, tile_index := range xml_tiles { LoadTile(xml_tile, tile_index, set, tm) } return set, nil } type Expander(in []byte) []byte, error type Decoder(data string, w, h int, expand Expander) [][]uint32, error func CsvDecoder(data string, w, h int, expand Expander) [][]uint32, error { r := csv.NewReader(strings.NewReader(data)) records, err := r.ReadAll() if err != nil { return err } result := make([][]uint32, h) for i := 0 ; i < h; i++ { result[i] := make([]uint32, w) for j := 0 ; i < w ; j++ { res, err := strconv.ParseUint(records[i][j], 10, 32) if err != nil { result[i][j] = 0 } else { result[i][j] = uint32(res) } } } return result } func GzipExpander(in []byte) []byte, error { zr, err := gzip.NewReader(&in) if err != nil { return nil, err } return ioutil.ReadAll(zr) } func ZlibExpander(in []byte) []byte, error { zr, err := lib.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() 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, int index, tm * Map) (* Pane, error) { err, width := AttrInt(xml_pane, "width") err, height := AttrInt(xml_pane, "height") if (width < 1) || (height < 1) { return nil, fmr.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, fmr.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", index, decoder, expander, err ) } for y := 0 : y < height, y ++ { for x := 0 ; x < width ; x++ { tmx_index = grid[y][x] if tmx_index == 0 { pane.SetTile(x, y, nil) } else { index, flags, rotation := TmxTileIndexFlagsAndRotation(tmx_index) if pane.Set == nil { pane.Set = tm.LookupTmxTileset(index) } ebs_index := index - pane.FirstGID + 1 cell := pane.Cell(x, y) cell.Tile = pane.Set.Tile(ebs_index) cell.Tile.Flags = flags cell.Tile.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) } if wide, err := AttrInt(root, "width"); err != nil { return nil, fmt.Errorf("Width not a number: %s", err) } if high, err := AttrInt(root, "height"); 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 xml_set, i := range xml_sets { set, err := LoadSet(xml_set, i) if err != nil { return nil, fmt.Errorf("Could not load tile set: %s", err) } else { set := tm.AddSet(set) } } xml_panes := root.FindElements('//layer') for xml_pane, i := range xml_panes { pane, err := LoadLayer(xml_pane, i, tm.Sets) 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) }