|
@@ -0,0 +1,513 @@
|
|
|
+package tile
|
|
|
+
|
|
|
+import import "log"
|
|
|
+// import "os"
|
|
|
+// import "math"
|
|
|
+
|
|
|
+import al "gitlab.com/beoran/al5go/al"
|
|
|
+import al "gitlab.com/beoran/ebsgo/engine/geometry/point"
|
|
|
+
|
|
|
+
|
|
|
+/** A tile set */
|
|
|
+type Set struct {
|
|
|
+ Tiles []Tile
|
|
|
+ Sheet * al.Bitmap
|
|
|
+ W int
|
|
|
+ H int
|
|
|
+ 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 point.P
|
|
|
+ /* 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 NewTileset(sheet * al.Bitmap, tile_w, tile_h, firstgid int) Set {
|
|
|
+ set := &Set{}
|
|
|
+ set.Sheet = sheet
|
|
|
+ set.TileW = tile_w
|
|
|
+ set.TileH = tile_h
|
|
|
+ set.FirstGID = firstgid
|
|
|
+ set.W = sheet.Width() / set.TileW
|
|
|
+ set.H = shet.Height() / set.TileH
|
|
|
+ size := set.W * set.H
|
|
|
+ set.Tiles = make([]Tile, size)
|
|
|
+ set.Last = 0
|
|
|
+ for := 0 ; i < size; i ++ {
|
|
|
+ set.Tiles[i].Init(set, i)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (tile * Tile) Init(set * Set, index int) {
|
|
|
+ tile.Tileset = set
|
|
|
+ tile.Index = index
|
|
|
+ tile.Wait = 0.25
|
|
|
+ tile.Recalculate()
|
|
|
+}
|
|
|
+
|
|
|
+func (tile Tile) SheetY(set Tileset) int {
|
|
|
+ (tile->Active * set->TileW) / (set->W * set->TileH)
|
|
|
+}
|
|
|
+
|
|
|
+func (tile Tile) SheetX(set Tileset) int {
|
|
|
+ (tile->Active * set->TileW) % (set->W)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/** Recalculates the tile's position (now) in it's tile set. */
|
|
|
+func (tile * Tile) Recalculate() {
|
|
|
+ if !tile.Tileset {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ x := float64(tile.SheetX(tile.Tileset))
|
|
|
+ y := float64(tile.SheetY(tile.Tileset))
|
|
|
+
|
|
|
+ tile.now = point.New(x, y)
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+func (set * Set) Get(index int) * Tile {
|
|
|
+ if nil != set && 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_ICE = iota
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* Helper lookup table for the tile flag names */
|
|
|
+var FlagNames := map[string]int {
|
|
|
+ "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 ,
|
|
|
+ "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)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** Initializes a tile's frame of animation. */
|
|
|
+func (frame * Frame) Init(index int, duration float64) {
|
|
|
+ frame.Index = index
|
|
|
+ frame.Duration = duration
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/** 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 tiles.Frames[index]
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** Gets the amount of Tiled style animations for this tile, or 0 if none. */
|
|
|
+int tile_frame_count(Tile * tile) {
|
|
|
+ if nil == tile.Frames {
|
|
|
+ return nil
|
|
|
+ } else {
|
|
|
+ return len(tiles.Frames)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/** Adds a Tiled-style animation frame to the tile. */
|
|
|
+Tile * tile_add_animation_frame(Tile * tile, int index, double duration) {
|
|
|
+ if (tile->frames) {
|
|
|
+ int size;
|
|
|
+ size = dynar_size(tile->frames);
|
|
|
+ if (!dynar_size_(tile->frames, size + 1)) return NULL;
|
|
|
+ tileframe_init(dynar_getdata(tile->frames, size), index, duration);
|
|
|
+ } else {
|
|
|
+ tile->frames = dynar_new(1, sizeof(struct TileFrame_));
|
|
|
+ if (!tile->frames) return NULL;
|
|
|
+ tileframe_init(dynar_getdata(tile->frames, 0), index, duration);
|
|
|
+ }
|
|
|
+ return tile;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/** Rewinds a tile's animations. */
|
|
|
+void tile_rewindanime(Tile * tile) {
|
|
|
+ if (!tile) return;
|
|
|
+ tile->active = tile->index;
|
|
|
+ tile->active_frame = 0;
|
|
|
+ // Finally recalculate tile position.
|
|
|
+ tile_recalculate(tile);
|
|
|
+}
|
|
|
+
|
|
|
+/** Updates a tile to animate it using classic style animation.*/
|
|
|
+void tile_update_classic(Tile * tile, double dt) {
|
|
|
+ int active = 0;
|
|
|
+ Tile * aidtile = NULL;
|
|
|
+ Tile * nowtile = tileset_get(tile->set, tile->active);
|
|
|
+ // nowtile is the tile that is currently active, that is shown.
|
|
|
+ // in stead of ourself, but it also may be ourself.
|
|
|
+ if(!nowtile) return;
|
|
|
+ tile->time += dt; // advance animation time of tile.
|
|
|
+ // Don't animate if not enough time has passed
|
|
|
+ if(tile->time < tile->wait) return;
|
|
|
+ // if we get here, reset animation time.
|
|
|
+ tile->time = 0.0;
|
|
|
+ // take the animation parameter and add it to the active
|
|
|
+ active = tile->active + nowtile->anim;
|
|
|
+ aidtile = tileset_get(tile->set, active);
|
|
|
+ // Check if there is such a tile.
|
|
|
+ if(!aidtile) return;
|
|
|
+ // If there is no such tile, don't change the active tile of this tile.
|
|
|
+ tile->active = active;
|
|
|
+ // Finally recalculate tile position.
|
|
|
+ tile_recalculate(tile);
|
|
|
+}
|
|
|
+
|
|
|
+/** Updates a tile to anmate it using TMX style animation. */
|
|
|
+void tile_update_tmx(Tile * tile, double dt) {
|
|
|
+ int active = 0;
|
|
|
+ TileFrame * frame = NULL;
|
|
|
+ Tile * aidtile = NULL;
|
|
|
+
|
|
|
+ frame = tile_frame(tile, tile->active_frame);
|
|
|
+ if (!frame) { /* Animation overshoit itself somehow??? */
|
|
|
+ tile->active_frame = 0;
|
|
|
+ frame = tile_frame(tile, tile->active_frame);
|
|
|
+ if (!frame) return;
|
|
|
+ }
|
|
|
+
|
|
|
+ tile->time += dt; // advance animation time of tile.
|
|
|
+ // Don't animate if not enough time has passed
|
|
|
+ if(tile->time < frame->duration) return;
|
|
|
+ // advance the animation frame, loop it around if needed.
|
|
|
+ tile->active_frame++;
|
|
|
+ if (tile->active_frame >= tile_frame_count(tile)) {
|
|
|
+ tile->active_frame = 0;
|
|
|
+ }
|
|
|
+ // Get new tile frame
|
|
|
+ frame = tile_frame(tile, tile->active_frame);
|
|
|
+ // If we get here, reset animation time.
|
|
|
+ tile->time = 0.0;
|
|
|
+ if (!frame) return;
|
|
|
+
|
|
|
+ // Get the active tile t use from the animation frame
|
|
|
+ active = frame->index;
|
|
|
+ aidtile = tileset_get(tile->set, active);
|
|
|
+ // Check if there is such a tile.
|
|
|
+ if(!aidtile) return;
|
|
|
+ // If there is no such tile, don't change the active tile of this tile.
|
|
|
+ tile->active = active;
|
|
|
+ // Finally recalculate tile position.
|
|
|
+ tile_recalculate(tile);
|
|
|
+ // tile->now = aidtile->now;
|
|
|
+ // LOG_NOTE("TMX Anim: %d (%d: set(%d, %d)): (x,y)=(%lf, %lf)\n", tile->index, tile->active, tile->set->w, tile->set->h, tile->now.x, tile->now.y);
|
|
|
+}
|
|
|
+
|
|
|
+/* Animates the tile. If it has a TMX style animation, that takes
|
|
|
+ * precedence, otherwise, use the classic style animation. */
|
|
|
+void tile_update(Tile * tile, double dt) {
|
|
|
+ if (tile->frames) {
|
|
|
+ tile_update_tmx(tile, dt);
|
|
|
+ } else {
|
|
|
+ tile_update_classic(tile, dt);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/** Updates all tiles in a tile set so they all get animated. */
|
|
|
+void tileset_update(Tileset * set, double dt) {
|
|
|
+ int index, size;
|
|
|
+ if (!set) return;
|
|
|
+ if (!set->tiles) return;
|
|
|
+ size = tileset_size(set);
|
|
|
+ for (index = 0; index < size; index++) {
|
|
|
+ Tile * tile = tileset_get(set, index);
|
|
|
+ tile_update(tile, dt);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/** Draw a tile to the current active drawing target at the
|
|
|
+given coordinates. Does nothing if tile is NULL. */
|
|
|
+void tile_draw(Tile * tile, int x, int y, int drawflags) {
|
|
|
+ Tileset * set;
|
|
|
+ Image * sheet;
|
|
|
+ Color dcolor = al_map_rgb(0xee, 0xee, 0x00);
|
|
|
+ if (!tile) return;
|
|
|
+ set = tile->set;
|
|
|
+ sheet = set->sheet;
|
|
|
+ float dx = (float) x;
|
|
|
+ float dy = (float) y;
|
|
|
+ float sx = (float) tile->now.x;
|
|
|
+ float sy = (float) tile->now.y;
|
|
|
+ float sw = (float) TILE_W;
|
|
|
+ float sh = (float) TILE_H;
|
|
|
+ al_draw_bitmap_region(sheet, sx, sy, sw, sh, dx, dy, drawflags);
|
|
|
+ // debugging solid tiles
|
|
|
+#ifdef TILE_SHOW_SOLID
|
|
|
+ if (tile_isflag(tile, TILE_WALL)) {
|
|
|
+ al_draw_rectangle(dx, dy, dx+TILE_W, dy+TILE_H, dcolor, 2);
|
|
|
+ }
|
|
|
+#endif // TILE_SHOW_SOLID
|
|
|
+
|
|
|
+ // al_draw_bitmap(sheet, dx, dy, 0);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* Used for drawing masked tiles. */
|
|
|
+static Image * tile_mask_buffer = NULL;
|
|
|
+
|
|
|
+
|
|
|
+/** Draw a tile into the given bitmap, which should be of size TILE_W, TILE_H
|
|
|
+ * applying the given mask bitmap, where the mask will
|
|
|
+be flipped and rotated as per the given mask_flags. The mask bitmap
|
|
|
+should be white, but with different alpha levels on the white
|
|
|
+which will be applied as the mask. Does nothing if tile is NULL.
|
|
|
+This requires al_hold_bitmap_drawing to be turned off!
|
|
|
+*/
|
|
|
+void tile_draw_masked_to
|
|
|
+(Image * result, Tile * tile, Image * mask, float angle, int mask_flags) {
|
|
|
+ /* This function need a mask buffer. */
|
|
|
+ Tileset * set;
|
|
|
+ Image * sheet;
|
|
|
+ ALLEGRO_BITMAP * target;
|
|
|
+ Color dcolor = al_map_rgb(0xee, 0x00, 0xee);
|
|
|
+ float dx, dy, sx, sy, sw, sh;
|
|
|
+ int bmpflags;
|
|
|
+
|
|
|
+ if (!tile) return;
|
|
|
+
|
|
|
+ /* Create a 32x32 tile bitmap that will be reused thanks to
|
|
|
+ it being static. And leaked at program shutdown, but I don't care :p. */
|
|
|
+ if (!tile_mask_buffer) {
|
|
|
+ bmpflags = al_get_new_bitmap_flags();
|
|
|
+ al_set_new_bitmap_flags(ALLEGRO_CONVERT_BITMAP);
|
|
|
+ tile_mask_buffer = al_create_bitmap(TILE_W, TILE_H);
|
|
|
+ al_set_new_bitmap_flags(bmpflags);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Keep the target bitmap. */
|
|
|
+ target = al_get_target_bitmap();
|
|
|
+
|
|
|
+ /* Copy the tile into the buffer. */
|
|
|
+ al_set_target_bitmap(tile_mask_buffer);
|
|
|
+ set = tile->set;
|
|
|
+ sheet = set->sheet;
|
|
|
+ dx = 0.0;
|
|
|
+ dy = 0.0;
|
|
|
+ sx = (float) tile->now.x;
|
|
|
+ sy = (float) tile->now.y;
|
|
|
+ sw = (float) TILE_W;
|
|
|
+ sh = (float) TILE_H;
|
|
|
+ /* Set blender to copy mode. */
|
|
|
+ // al_clear_to_color(al_map_rgba_f(0,0,0,0));
|
|
|
+
|
|
|
+ al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_ZERO);
|
|
|
+ al_draw_bitmap_region(sheet, sx, sy, sw, sh, 0, 0, 0);
|
|
|
+
|
|
|
+ /* Draw the mask over the tile, taking the alpha of the mask */
|
|
|
+ al_set_blender(ALLEGRO_ADD, ALLEGRO_ZERO, ALLEGRO_ALPHA);
|
|
|
+ al_draw_bitmap(mask, 0, 0, mask_flags);
|
|
|
+
|
|
|
+ /* Restore normal Allegro blending. */
|
|
|
+ al_set_blender(ALLEGRO_ADD, ALLEGRO_ONE, ALLEGRO_INVERSE_ALPHA);
|
|
|
+
|
|
|
+ sx = 0.0;
|
|
|
+ sy = 0.0;
|
|
|
+ if (angle != 0.0) {
|
|
|
+ sx = TILE_H / 2.0;
|
|
|
+ sy = TILE_W / 2.0;
|
|
|
+ dx += sx;
|
|
|
+ dy += sy;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Draw the tile mask buffer to the result bitmap */
|
|
|
+ al_set_target_bitmap(result);
|
|
|
+ al_draw_rotated_bitmap(tile_mask_buffer, sx, sy, dx, dy, angle, 0);
|
|
|
+ /* And restore the target bitmap. */
|
|
|
+ al_set_target_bitmap(target);
|
|
|
+}
|
|
|
+
|
|
|
+/** Tile's index. Returns -1 if tile is NULL; */
|
|
|
+int tile_index(Tile * tile) {
|
|
|
+ if (!tile) return -1;
|
|
|
+ return tile->index;
|
|
|
+}
|
|
|
+
|
|
|
+/** Information about the tile's properties. Return -1 if tile is NULL. */
|
|
|
+int tile_kind(Tile * tile) {
|
|
|
+ if (!tile) return -1;
|
|
|
+ return tile->kind;
|
|
|
+}
|
|
|
+
|
|
|
+/** Information about tile's blending properties and priority.
|
|
|
+ * Zero means no blending, positive is a blend priority.
|
|
|
+ */
|
|
|
+int tile_blend(Tile * tile) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->blend;
|
|
|
+}
|
|
|
+
|
|
|
+/** Set the tile's blending and priority */
|
|
|
+int tile_blend_(Tile * tile, int priority) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->blend = priority;
|
|
|
+}
|
|
|
+
|
|
|
+/** Information about tile's blending mask
|
|
|
+ * Returns 0 for the default mask if not set.
|
|
|
+ */
|
|
|
+int tile_blend_mask(Tile * tile) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->blend_mask;
|
|
|
+}
|
|
|
+
|
|
|
+/** Set the tile's blending mask */
|
|
|
+int tile_blend_mask_(Tile * tile, int mask) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ if (mask < 0) mask = 0;
|
|
|
+ if (mask > 2) mask = 0;
|
|
|
+ return tile->blend_mask = mask;
|
|
|
+}
|
|
|
+
|
|
|
+/** Get the tile's light flag. Zero means no lighting. */
|
|
|
+int tile_light(Tile * tile) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->light;
|
|
|
+}
|
|
|
+
|
|
|
+/** Set the tile's light flag */
|
|
|
+int tile_light_(Tile * tile, int value) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->light = value;
|
|
|
+}
|
|
|
+
|
|
|
+/** Information about tile's lighting mask
|
|
|
+ * Returns 0 for the default mask if not set.
|
|
|
+ */
|
|
|
+int tile_light_mask(Tile * tile) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->light_mask;
|
|
|
+}
|
|
|
+
|
|
|
+/** Set the tile's light mask */
|
|
|
+int tile_light_mask_(Tile * tile, int mask) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ if (mask < 0) mask = 0;
|
|
|
+ return tile->light_mask = mask;
|
|
|
+}
|
|
|
+
|
|
|
+/** Get the tile's shadow flag. Zero means no autoshadow. */
|
|
|
+int tile_shadow(Tile * tile) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->shadow;
|
|
|
+}
|
|
|
+
|
|
|
+/** Set the tile's light flag */
|
|
|
+int tile_shadow_(Tile * tile, int value) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->shadow = value;
|
|
|
+}
|
|
|
+
|
|
|
+/** Information about tile's shadow mask
|
|
|
+ * Returns 0 for the default mask if not set.
|
|
|
+ */
|
|
|
+int tile_shadow_mask(Tile * tile) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ return tile->shadow_mask;
|
|
|
+}
|
|
|
+
|
|
|
+/** Set the tile's shadow mask */
|
|
|
+int tile_shadow_mask_(Tile * tile, int mask) {
|
|
|
+ if (!tile) return 0;
|
|
|
+ if (mask < 0) mask = 0;
|
|
|
+ return tile->shadow_mask = mask;
|
|
|
+}
|
|
|
+
|