package tile import "fmt" import "gitlab.com/beoran/ebsgo/monolog" // import "os" // import "math" import "gitlab.com/beoran/al5go/al" import "gitlab.com/beoran/ebsgo/engine/geometry" const TILE_W = 32 const TILE_H = 32 var showSolid = false; /** A tile set */ type Set struct { Tiles []Tile Sheet * al.Bitmap W int // Width, in tiles H int // Height, in tiles TileW int TileH int FirstGID int /* Offset of tile set in TMX map file. Used to correct tile offsets. */ }; /** A Tiled-style animation frame of a tile. */ type Frame struct { Index int /* Tile set index for this frame of animation. */ Duration float64 /* Duration of the frame in s. */ } /** * A single tile from a tile map. * Tiles can be animated. This works like this: a tile has an animation * pointer and offset which points to the next tile to be drawn in the tileset. */ type Tile struct { Tileset * Set /* Tileset this tile belongs to */ Index int /* Index in the tile set. */ Flags int /* Information about the tile's properties. */ Kind int /* Offset to the tile to skip to when animating. If this is 0 the tile is not animated. If nonzero, the tile will skip to the tile in the same tile set set with index index + anim. May be negative to "roll back" an animation to it's begin. */ Anim int /** For unanimated tiles, active is set to the index of the tile itself. For animated tiles, it is set to the index of tile that currently should be displayed in stead of this tile due to animation. */ Active int Wait float64 /* Time in s to wait before jumping to the next frame of this tile. */ Time float64 /* Time since last animation in s. */ Position geometry.Vector /* Automatic blending activation and priority. */ Blend int /* Mask number to use for automatic blending, if any. */ BlendMask int /* Automatic lighting activation flag. */ Light int LightMask int /* Automatic shadow activation flag. */ Shadow int /* Automatic shadow mask number. */ ShadowMask int /* Tiled-style animation frames. */ Frames []*Frame /* Active frame for TMX-style animations. */ ActiveFrame int }; /* NOTE: Tiles could be implemented using sub bitmaps as they seem to be * slightly faster if they are preallocated. however the speed gain would * be around 2%, so it's not a priority yet. It could simplify some of * the code, though. */ func NewSet(sheet * al.Bitmap, tile_w, tile_h, firstgid int) * Set { if sheet == nil { return nil } set := &Set{} set.Sheet = sheet set.TileW = tile_w set.TileH = tile_h set.FirstGID = firstgid set.W = sheet.Width() / set.TileW set.H = sheet.Height() / set.TileH size := set.W * set.H set.Tiles = make([]Tile, size) for i := 0 ; i < size; i ++ { set.Tiles[i].Init(set, i) } return set } func (set * Set) Close() { if set.Sheet != nil { set.Sheet.Destroy() set.Sheet = nil } } func (tile * Tile) Init(set * Set, index int) { tile.Tileset = set tile.Index = index tile.Active = index tile.Wait = 0.25 tile.Recalculate() } func (tile Tile) SheetY(set * Set) int { return (tile.Active / set.W) * set.TileH } func (tile Tile) SheetX(set * Set) int { return (tile.Active % set.W) * set.TileW } /** Recalculates the tile's position (now) in it's tile set. */ func (tile * Tile) Recalculate() { if nil == tile.Tileset { return } x := float32(tile.SheetX(tile.Tileset)) y := float32(tile.SheetY(tile.Tileset)) tile.Position = geometry.NewVector(x, y) } func (set Set) Tile(index int) * Tile { if index >= 0 && index <= len(set.Tiles) { return &set.Tiles[index] } return nil } /** Tile types */ const ( TILE_NONE = 0 TILE_WALL = iota TILE_WATER = iota TILE_LEDGE = iota TILE_STAIR = iota TILE_PUSH = iota TILE_NORTH = iota TILE_SOUTH = iota TILE_EAST = iota TILE_WEST = iota TILE_UP = iota TILE_DOWN = iota TILE_ICE = iota ) /* Helper lookup table for the tile flag names */ var FlagNames map[string]uint = map[string]uint { "wall" : TILE_WALL , "water": TILE_WATER , "ledge": TILE_LEDGE , "stair": TILE_STAIR , "push" : TILE_PUSH , "north": TILE_NORTH , "south": TILE_SOUTH , "east" : TILE_EAST , "west" : TILE_WEST , "up" : TILE_UP , "down" : TILE_DOWN , "ice" : TILE_ICE , } /** Sets a tile's flags from a property string. * This uses an internal lookup table. */ func (tile * Tile) SetProperty(property string) { val, ok := FlagNames[property]; if (ok) { tile.Flags |= (1 << val) } } func (tile * Tile) HasFlag(flag uint) bool { return (tile.Flags & (1 << flag)) == (1 << flag) } func (tile * Tile) IsWall() bool { return tile.HasFlag(TILE_WALL) } /** Initializes a tile's frame of animation. */ func (frame * Frame) Init(index int, duration float64) { frame.Index = index frame.Duration = duration } func (frame Frame) String() string { return fmt.Sprintf("frame: %d", frame.Index) } /** Gets the nth frame of Tiled style animations for this tile * or NULL if no such animation frame. */ func (tile Tile) Frame(index int) * Frame { if nil == tile.Frames{ return nil } else { return tile.Frames[index] } } /** Gets the amount of Tiled style animations for this tile, or 0 if none. */ func (tile Tile) FrameCount() int { if nil == tile.Frames { return 0 } else { return len(tile.Frames) } } /** Adds a Tiled-style animation frame to the tile. */ func (tile * Tile) AddAnimationFrame(index int, duration float64) (frame * Frame) { frame = &Frame{} tile.Frames = append(tile.Frames, frame) frame.Init(index, duration) return frame } /** Rewinds a tile's animations. */ func (tile * Tile) RewindAnimations() { tile.Active = tile.Index tile.ActiveFrame = 0 // Finally recalculate tile position. tile.Recalculate() } /** Updates a tile to animate it using TMX style animation. */ func (tile * Tile) UpdateAnimation(dt float64) { active := 0; frame := tile.Frame(tile.ActiveFrame) if nil == frame { /* Animation overshot itself */ tile.ActiveFrame = 0 frame = tile.Frame(tile.ActiveFrame) if nil == frame { /* Tile has no frames at all. */ return } } /* monolog.Log("TILE", "Animation for tile: %d %d %f %f\n", tile.ActiveFrame, frame.Index, frame.Duration, tile.Time) */ tile.Time += dt // advance animation time of tile. // Don't animate if not enough time has passed if tile.Time < (frame.Duration * 10.0) { return } // advance the animation frame, loop it around if needed. tile.ActiveFrame++ if tile.ActiveFrame >= tile.FrameCount() { tile.ActiveFrame = 0 } // Get new tile frame frame = tile.Frame(tile.ActiveFrame); // If we get here, reset animation time. tile.Time = 0.0 if nil == frame { return } monolog.Log("TILE", "Animation for tile: %d %d %d %d %v\n", tile.ActiveFrame, frame.Index, frame.Duration, tile.Time, tile.Frames) // Get the active tile to use from the animation frame active = frame.Index aidtile := tile.Tileset.Tile(active); // Check if there is such a tile. if nil == aidtile { return } // If there is such a tile, change the active tile index of this tile. tile.Active = active // Finally recalculate tile position. tile.Recalculate() monolog.Log("TILE", "Updated animation for tile: %d %d\n", tile.Active, frame.Index) } /* Animates the tile. Animates the tile if it has animation frames. */ func (tile * Tile) Update(dt float64) { if nil != tile.Frames { tile.UpdateAnimation(dt) } } /** Updates all tiles in a tile set so they all get animated. */ func (set * Set) Update(dt float64) { if nil == set.Tiles { return } for i := 0 ; i < len(set.Tiles); i ++ { tile := &set.Tiles[i] tile.Update(dt) } } /** Recalculates all tiles in a tile set so they all get the rght position. */ func (set * Set) Recalculate() { if nil == set.Tiles { return } for i := 0 ; i < len(set.Tiles); i ++ { tile := &set.Tiles[i] tile.Recalculate() } } /** Draw a tile to the current active drawing target at the given coordinates. Does nothing if tile is NULL. */ func (tile Tile) Draw(x, y float32, drawflags int) { set := tile.Tileset sheet := set.Sheet dx := float32(x) dy := float32(y) sx := float32(tile.Position.X) sy := float32(tile.Position.Y) sw := float32(set.TileW) sh := float32(set.TileH) sheet.DrawRegion(sx, sy, sw, sh, dx, dy, drawflags); // debugging solid tiles if showSolid { if (tile.Flags & (1<