io.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. package tile
  2. import "gitlab.com/beoran/ebsgo/monolog"
  3. import "fmt"
  4. import "strings"
  5. import "bytes"
  6. import "strconv"
  7. import "io/ioutil"
  8. import "compress/gzip"
  9. import "compress/zlib"
  10. import "encoding/binary"
  11. import "encoding/base64"
  12. import "gitlab.com/beoran/al5go/al"
  13. // import "gitlab.com/beoran/ebsgo/engine/geometry/point"
  14. // import "gitlab.com/beoran/ebsgo/engine/physics/camera"
  15. import "gitlab.com/beoran/ebsgo/engine/fifi"
  16. import "github.com/beevik/etree"
  17. // Tile flipping constants
  18. const (
  19. TMX_FLIPPED_HORIZONTALLY = 0x80000000
  20. TMX_FLIPPED_VERTICALLY = 0x40000000
  21. TMX_FLIPPED_DIAGONALLY = 0x20000000
  22. TMX_FLIPPED_FILTER = (^uint32(0xE0000000))
  23. )
  24. func AttrInt(e * etree.Element, key string) (int, error) {
  25. return strconv.Atoi(e.SelectAttrValue(key, ""))
  26. }
  27. func AttrFloat64(e * etree.Element, key string) (float64, error) {
  28. return strconv.ParseFloat(e.SelectAttrValue(key, ""), 64)
  29. }
  30. func Property(e * etree.Element, propname string) (string, bool) {
  31. properties := e.FindElements("//properties/property")
  32. for _, property := range properties {
  33. name := property.SelectAttrValue("name", "")
  34. if name == propname {
  35. return property.SelectAttrValue("value", ""), true
  36. }
  37. }
  38. return "", false
  39. }
  40. func PropertyInt(e * etree.Element, propname string) (int, error) {
  41. value , ok := Property(e, propname)
  42. if (!ok) {
  43. return 0, fmt.Errorf("no such property")
  44. }
  45. return strconv.Atoi(value)
  46. }
  47. func LoadFrame(xml_frame * etree.Element, tile * Tile, set * Set) * Tile {
  48. tile_id, err := AttrInt(xml_frame, "tileid")
  49. if err != nil {
  50. return tile;
  51. }
  52. // the tile id obtained here is correct and doesn't need to be
  53. // lessened by firstgid
  54. duration, err := AttrFloat64(xml_frame, "duration")
  55. if err != nil {
  56. return tile;
  57. }
  58. /* Convert ms to s. */
  59. duration /= 1000.0
  60. frame := tile.AddAnimationFrame(tile_id, duration)
  61. monolog.Log("TILEIO", "Frame loaded: %d %f", frame.Index, frame.Duration)
  62. return tile
  63. }
  64. func LoadAnimation(xml_tile * etree.Element, tile * Tile, set * Set) * Tile {
  65. xml_anim := xml_tile.FindElement("//animation")
  66. if xml_anim == nil {
  67. return tile;
  68. }
  69. xml_frames := xml_anim.FindElements("//frame")
  70. for _, xml_frame := range xml_frames {
  71. LoadFrame(xml_frame, tile, set)
  72. }
  73. monolog.Log("TILEIO", "Loaded animation: %d", len(tile.Frames))
  74. return tile
  75. }
  76. func XmlToTileType(xml_tile * etree.Element) (uint, error) {
  77. value , ok := Property(xml_tile, "type")
  78. if (!ok) {
  79. return 0, fmt.Errorf("no type property")
  80. }
  81. result, ok := FlagNames[value]
  82. if ! ok {
  83. return 0, fmt.Errorf("unknown type property")
  84. }
  85. return result, nil
  86. }
  87. func LoadTile(xml_tile * etree.Element, index int, set * Set, tm * Map) (*Tile, error) {
  88. id, err := AttrInt(xml_tile, "id")
  89. if err != nil {
  90. return nil, fmt.Errorf("Tile id not found: %s", err)
  91. }
  92. tile := set.Tile(id)
  93. monolog.Log("TILEIO", "Got tile: %d <- %d", tile.Active, tile.Index)
  94. if tile == nil {
  95. return nil, fmt.Errorf("Tile id not found in tile set: %d", id)
  96. }
  97. value , ok := Property(xml_tile, "type")
  98. if (ok) {
  99. parts := strings.Split(value, ",")
  100. for _, part := range parts {
  101. clean := strings.Trim(part, "")
  102. tile.SetProperty(clean)
  103. }
  104. }
  105. // ianim , err := PropertyInt(xml_tile, "anim" )
  106. // iwait , err := PropertyInt(xml_tile, "wait" )
  107. ilight , err := PropertyInt(xml_tile, "light" )
  108. iblend , err := PropertyInt(xml_tile, "blend" )
  109. ishadow , err := PropertyInt(xml_tile, "shadow" )
  110. ilightmask , err := PropertyInt(xml_tile, "lightmask" )
  111. iblendmask , err := PropertyInt(xml_tile, "blendmask" )
  112. ishadowmask , err := PropertyInt(xml_tile, "shadowmask" )
  113. // No support anymore for "classic" animations since TMX now
  114. // supports animations.
  115. tile.Light = ilight
  116. tile.Blend = iblend
  117. tile.Shadow = ishadow
  118. tile.LightMask = ilightmask
  119. tile.BlendMask = iblendmask
  120. tile.ShadowMask = ishadowmask
  121. /* Support tiled-style animations. */
  122. LoadAnimation(xml_tile, tile, set)
  123. monolog.Log("TILEIO", "Loaded tile: (%d x %d)", tile.SheetX(tile.Tileset), tile.SheetY(tile.Tileset))
  124. return tile, nil
  125. }
  126. /** Calculates index and the Allegro draw flags and rotation for a tile. */
  127. func TmxTileIndexFlagsAndRotation(tmx_index uint32) (int, int, float32) {
  128. index := int(tmx_index & TMX_FLIPPED_FILTER)
  129. flags := int(0)
  130. rotate := float32(0.0)
  131. if (tmx_index & TMX_FLIPPED_HORIZONTALLY) != 0 {
  132. flags |= al.FLIP_HORIZONTAL
  133. }
  134. if (tmx_index & TMX_FLIPPED_VERTICALLY) != 0 {
  135. flags |= al.FLIP_VERTICAL
  136. }
  137. if (tmx_index & TMX_FLIPPED_DIAGONALLY) != 0 {
  138. rotate = float32(al.PI / 2.0)
  139. }
  140. return index, flags, rotate
  141. }
  142. func LoadSet(xml_set * etree.Element, index int, tm * Map) (* Set, error) {
  143. firstgid, err := AttrInt(xml_set, "firstgid")
  144. if err != nil {
  145. return nil, fmt.Errorf("Could not find firstgid for tile set %d: %s", index, err)
  146. }
  147. xml_image := xml_set.FindElement("//image")
  148. if xml_image == nil {
  149. return nil, fmt.Errorf("Could not find image data for tile set %d", index)
  150. }
  151. tile_w, err := AttrInt(xml_set, "tilewidth")
  152. tile_h, err := AttrInt(xml_set, "tilewidth")
  153. if (tile_w < 1) || (tile_h < 1) {
  154. return nil, fmt.Errorf("Tile size too small for tile set %d: %d x %d", index, tile_w, tile_h)
  155. }
  156. iname := xml_image.SelectAttrValue("source", "tile_set_default.png")
  157. bmp := fifi.LoadBitmap("map/" + iname)
  158. if bmp == nil {
  159. return nil, fmt.Errorf("Could not load tile set sheet: %s", iname)
  160. }
  161. set := NewSet(bmp, tile_w, tile_h, firstgid)
  162. monolog.Log("TILEIO", "Loaded tile set: (%d x %d) (%d x %d) %d", set.TileW, set.TileH, set.W, set.H, set.FirstGID)
  163. xml_tiles := xml_set.FindElements("//tile")
  164. for tile_index, xml_tile := range xml_tiles {
  165. LoadTile(xml_tile, tile_index, set, tm)
  166. }
  167. set.Recalculate()
  168. return set, nil
  169. }
  170. type Expander func(in []byte) ([]byte, error)
  171. type Decoder func(data string, w, h int, expand Expander) ([][]uint32, error)
  172. func CsvDecoder(data string, w, h int, expand Expander) ([][]uint32, error) {
  173. lines := strings.Split(data, "\n")
  174. x := 0
  175. y := 0
  176. result := make([][]uint32, h)
  177. for _, line := range lines {
  178. if line != "" {
  179. cells := strings.Split(line, ",")
  180. result[y] = make([]uint32, w)
  181. for x = 0 ; x < w ; x++ {
  182. value := uint32(0)
  183. if ( x >= len(cells) ) {
  184. break;
  185. }
  186. res, err := strconv.ParseUint(cells[x], 10, 32)
  187. if err == nil {
  188. value = uint32(res)
  189. }
  190. result[y][x] = value
  191. }
  192. y++
  193. }
  194. if y >= h {
  195. break;
  196. }
  197. }
  198. return result, nil
  199. }
  200. func GzipExpander(in []byte) ([]byte, error) {
  201. zr, err := gzip.NewReader(bytes.NewReader(in))
  202. if err != nil {
  203. return nil, err
  204. }
  205. return ioutil.ReadAll(zr)
  206. }
  207. func ZlibExpander(in []byte) ([]byte, error) {
  208. zr, err := zlib.NewReader(bytes.NewReader(in))
  209. if err != nil {
  210. return nil, err
  211. }
  212. return ioutil.ReadAll(zr)
  213. }
  214. func Base64Decoder(data string, w, h int, expand Expander) ([][]uint32, error) {
  215. bytes, err := base64.StdEncoding.DecodeString(data)
  216. if err != nil {
  217. return nil, err
  218. }
  219. if expand != nil {
  220. bytes, err = expand(bytes)
  221. if err != nil {
  222. return nil, err
  223. }
  224. }
  225. result := make([][]uint32, h)
  226. for i := 0 ; i < h; i++ {
  227. result[i] = make([]uint32, w)
  228. for j := 0 ; i < w ; j++ {
  229. index := (i * w + j) * 4
  230. value := binary.LittleEndian.Uint32(bytes[index:4])
  231. result[i][j] = value
  232. }
  233. }
  234. return result, nil
  235. }
  236. var Decoders map[string]Decoder = map[string]Decoder {
  237. "csv" : CsvDecoder,
  238. "base64": Base64Decoder,
  239. }
  240. var Expanders map[string]Expander = map[string]Expander {
  241. "gzip" : GzipExpander,
  242. "zlib": ZlibExpander,
  243. }
  244. // Loads a single tile pane of the tile map from xml (tmx).
  245. func LoadPane(xml_pane * etree.Element, index int, tm * Map) (* Pane, error) {
  246. width, err := AttrInt(xml_pane, "width")
  247. height, err := AttrInt(xml_pane, "height")
  248. if (width < 1) || (height < 1) {
  249. return nil,
  250. fmt.Errorf("Layer %i size too small: %d x %d, ", index, width, height)
  251. }
  252. name := xml_pane.SelectAttrValue("name", "")
  253. iset, err := PropertyInt(xml_pane, "tileset")
  254. if err != nil {
  255. iset = 0
  256. }
  257. set := tm.Set(iset)
  258. if set == nil {
  259. return nil,
  260. fmt.Errorf("No tile set found for layer %d: %d", index, iset)
  261. }
  262. pane := NewPane(set, width, height, name)
  263. xml_data := xml_pane.FindElement("//data")
  264. if xml_data == nil {
  265. return nil, fmt.Errorf("Cannot find layer data for layer %d", index)
  266. }
  267. encoding := xml_data.SelectAttrValue("encoding" , "csv")
  268. compression := xml_data.SelectAttrValue("compression", "")
  269. decoder := Decoders[encoding]
  270. if decoder == nil {
  271. return nil, fmt.Errorf("Unknown encoding %s", encoding)
  272. }
  273. expander := Expanders[compression]
  274. grid, err := decoder(xml_data.Text(), width, height, expander)
  275. if err != nil {
  276. return nil, fmt.Errorf("Could not decode data for layer %d: %s %s: %s;\nData:>%s<",
  277. index, encoding, compression, err, xml_data.Text())
  278. }
  279. monolog.Log("TILEIO", "Loaded grid size: (%d x %d) vs size (%d x %d)\n",len(grid), len(grid[0]), height, width)
  280. for y := 0 ; y < height ; y ++ {
  281. row := grid[y]
  282. for x := 0 ; x < width && x < len(row) ; x++ {
  283. tmx_index := row[x]
  284. if tmx_index == 0 {
  285. pane.SetTile(x, y, nil)
  286. } else {
  287. index, flags, rotation := TmxTileIndexFlagsAndRotation(tmx_index)
  288. if pane.Tileset == nil {
  289. pane.Tileset = tm.LookupTmxTileset(index)
  290. }
  291. ebs_index := index - pane.FirstGID() // + 1
  292. cell := pane.Cell(x, y)
  293. cell.Tile = pane.Tileset.Tile(ebs_index)
  294. cell.Flags = flags
  295. cell.Rotate = rotation
  296. }
  297. }
  298. }
  299. return pane, nil
  300. }
  301. // Load a TMX map file from the given XML document.
  302. func LoadMapXml(doc * etree.Document) (* Map, error) {
  303. root := doc.Root()
  304. if root.Tag != "map" {
  305. return nil, fmt.Errorf("File is not a TMX map file: %s", root.Tag)
  306. }
  307. wide, err := AttrInt(root, "width")
  308. if err != nil {
  309. return nil, fmt.Errorf("Width not a number: %s", err)
  310. }
  311. high, err := AttrInt(root, "height");
  312. if err != nil {
  313. return nil, fmt.Errorf("Height not a number: %s", err)
  314. }
  315. if (wide < 1) || (high < 1) {
  316. return nil, fmt.Errorf("Map size too smalll: %d x %d",wide, high)
  317. }
  318. tm := NewMap(wide, high)
  319. xml_sets := root.FindElements("//tileset")
  320. for i, xml_set := range xml_sets {
  321. set, err := LoadSet(xml_set, i, tm)
  322. if err != nil {
  323. return nil, fmt.Errorf("Could not load tile set: %s", err)
  324. } else {
  325. _ = tm.AddSet(set)
  326. }
  327. }
  328. xml_panes := root.FindElements("//layer")
  329. for i, xml_pane := range xml_panes {
  330. pane, err := LoadPane(xml_pane, i, tm)
  331. if err != nil {
  332. return nil, fmt.Errorf("Could not load tile layer: %s", err)
  333. } else {
  334. tm.AddPane(pane)
  335. }
  336. }
  337. return tm, nil;
  338. }
  339. // Loads a TMX tile map from the named file.
  340. // Return nil on error.
  341. func LoadMap(filename string) (* Map, error) {
  342. doc := etree.NewDocument()
  343. err := doc.ReadFromFile(filename)
  344. if err != nil {
  345. return nil, err
  346. }
  347. return LoadMapXml(doc)
  348. }
  349. func LoadMapFifi(relname string) (* Map, error) {
  350. realname := fifi.Map(relname)
  351. return LoadMap(realname)
  352. }