283 lines
7.5 KiB
Go
283 lines
7.5 KiB
Go
package assets
|
|
|
|
import (
|
|
_ "image/png"
|
|
"log"
|
|
"openquell/config"
|
|
"openquell/util"
|
|
"strings"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/solarlune/ldtkgo"
|
|
)
|
|
|
|
var Project = LoadLDTK("levels")
|
|
var Tiles = InitTiles()
|
|
|
|
// Tile: contains image, identifier (as used in level data) and
|
|
// additional properties
|
|
type Tile struct {
|
|
Id, Ref string
|
|
Sprite *ebiten.Image
|
|
ToggleSprite *ebiten.Image
|
|
Solid bool // wall brick
|
|
Player bool // player sphere
|
|
IsPrimary bool // primary player sphere
|
|
Renderable bool // visible, has sprite
|
|
Velocity bool // movable
|
|
Collectible bool // collectible, vanishes once collected
|
|
Transient bool // turns into brick wall when traversed
|
|
Destroyable bool // turns into empty floor when bumped into twice
|
|
Animation int // -1=unused, 0-3 = show image of slice
|
|
Tiles []*ebiten.Image // has N sprites
|
|
TileNames []string // same thing, only the names
|
|
Obstacle bool // is an obstacle/enemy
|
|
Direction int // obstacle business end shows into this direction
|
|
Shader *ebiten.Shader
|
|
Alpha *ebiten.Image
|
|
Bond bool // denotes an entity which can have a relation to another
|
|
Door bool // a door, can be manipulated by a switch
|
|
Switch bool // opens|closes a door
|
|
AnimateOnDestruct bool // wether to animate destruction
|
|
AnimationSpriteSheet AnimationSet // which sprites to use (refers to an entry in assets.Animations[name])
|
|
}
|
|
|
|
func (tile *Tile) Clone() *Tile {
|
|
newtile := &Tile{
|
|
Id: tile.Id,
|
|
Ref: tile.Ref,
|
|
Sprite: tile.Sprite,
|
|
ToggleSprite: tile.ToggleSprite,
|
|
Solid: tile.Solid,
|
|
Player: tile.Player,
|
|
IsPrimary: tile.IsPrimary,
|
|
Renderable: tile.Renderable,
|
|
Velocity: tile.Velocity,
|
|
Collectible: tile.Collectible,
|
|
Transient: tile.Transient,
|
|
Destroyable: tile.Destroyable,
|
|
Animation: tile.Animation,
|
|
Tiles: tile.Tiles,
|
|
TileNames: tile.TileNames,
|
|
Obstacle: tile.Obstacle,
|
|
Direction: tile.Direction,
|
|
Alpha: tile.Alpha,
|
|
Shader: tile.Shader,
|
|
Bond: tile.Bond,
|
|
Door: tile.Door,
|
|
Switch: tile.Switch,
|
|
AnimateOnDestruct: tile.AnimateOnDestruct,
|
|
AnimationSpriteSheet: tile.AnimationSpriteSheet,
|
|
}
|
|
|
|
return newtile
|
|
}
|
|
|
|
const (
|
|
Primary bool = true
|
|
Secondary bool = false
|
|
)
|
|
|
|
func GetSprites(class []string) []*ebiten.Image {
|
|
sprites := []*ebiten.Image{}
|
|
|
|
for _, sprite := range class {
|
|
sprites = append(sprites, Assets[sprite])
|
|
}
|
|
|
|
return sprites
|
|
}
|
|
|
|
func NewTilePlayer(isprimary bool) *Tile {
|
|
tile := &Tile{
|
|
Renderable: true,
|
|
Player: true,
|
|
Velocity: true,
|
|
IsPrimary: isprimary,
|
|
}
|
|
|
|
switch isprimary {
|
|
case Primary:
|
|
tile.Sprite = Assets["sphere-blue"]
|
|
case Secondary:
|
|
tile.Sprite = Assets["sphere-blue-secondary"]
|
|
}
|
|
|
|
// primary sprite is always the first one
|
|
tile.Tiles = []*ebiten.Image{Assets["sphere-blue"], Assets["sphere-blue-secondary"]}
|
|
return tile
|
|
}
|
|
|
|
func NewTileBlock() *Tile {
|
|
return &Tile{
|
|
Solid: true,
|
|
Renderable: true,
|
|
}
|
|
}
|
|
|
|
func NewTileCollectible() *Tile {
|
|
return &Tile{
|
|
Solid: false,
|
|
Renderable: true,
|
|
Collectible: true,
|
|
}
|
|
}
|
|
|
|
func NewTileObstacle(direction int) *Tile {
|
|
return &Tile{
|
|
Solid: false,
|
|
Renderable: true,
|
|
Obstacle: true,
|
|
Direction: direction,
|
|
Velocity: true,
|
|
}
|
|
}
|
|
|
|
func NewTileAnimation(class []string) *Tile {
|
|
sprites := GetSprites(class)
|
|
|
|
return &Tile{
|
|
Solid: false,
|
|
Renderable: false,
|
|
Animation: 0,
|
|
Tiles: sprites,
|
|
}
|
|
}
|
|
|
|
func NewTileTranswall() *Tile {
|
|
return &Tile{
|
|
Solid: false,
|
|
Renderable: true,
|
|
Transient: true,
|
|
}
|
|
}
|
|
|
|
func NewTileHiddenDoor(alpha string) *Tile {
|
|
return &Tile{
|
|
Solid: false,
|
|
Renderable: true,
|
|
Destroyable: true,
|
|
Alpha: Assets[alpha],
|
|
Shader: Shaders["destruct"],
|
|
}
|
|
}
|
|
|
|
func NewTileSwitch() *Tile {
|
|
return &Tile{
|
|
Solid: false,
|
|
Renderable: true,
|
|
Bond: true,
|
|
Switch: true,
|
|
}
|
|
}
|
|
|
|
func NewTileDoor() *Tile {
|
|
return &Tile{
|
|
Solid: false,
|
|
Renderable: true,
|
|
Bond: true,
|
|
Door: true,
|
|
}
|
|
}
|
|
|
|
// used to map level data bytes to actual tiles
|
|
type TileRegistry map[string]*Tile
|
|
|
|
func InitTiles() TileRegistry {
|
|
return TileRegistry{
|
|
"floor": {Renderable: false},
|
|
"default": NewTileBlock(),
|
|
"solidorange": NewTileBlock(),
|
|
"PlayerPrimary": NewTilePlayer(Primary),
|
|
"PlayerSecondary": NewTilePlayer(Secondary),
|
|
"Collectible": NewTileCollectible(),
|
|
"ObstacleStar": NewTileObstacle(config.All),
|
|
"ObstacleNorth": NewTileObstacle(config.North),
|
|
"ObstacleSouth": NewTileObstacle(config.South),
|
|
"ObstacleWest": NewTileObstacle(config.West),
|
|
"ObstacleEast": NewTileObstacle(config.East),
|
|
"Animation": NewTileAnimation([]string{
|
|
"collectible-detonating1",
|
|
"collectible-detonating2",
|
|
"collectible-detonating3",
|
|
"collectible-detonating4",
|
|
"collectible-detonating5",
|
|
"collectible-detonating6",
|
|
"collectible-detonating7",
|
|
"collectible-detonating8",
|
|
"collectible-detonating9",
|
|
"collectible-detonating10",
|
|
"collectible-detonating11",
|
|
"collectible-detonating12",
|
|
"collectible-detonating13",
|
|
"collectible-detonating14",
|
|
"collectible-detonating15",
|
|
}),
|
|
"Transient": NewTileTranswall(),
|
|
"HiddenDoor": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor2": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor3": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor4": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor5": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor6": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor7": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor8": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor9": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor10": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor11": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor12": NewTileHiddenDoor("damage"),
|
|
"HiddenDoor13": NewTileHiddenDoor("damage"),
|
|
"Switch": NewTileSwitch(),
|
|
"Door": NewTileDoor(),
|
|
}
|
|
}
|
|
|
|
// load LDTK project at compile time into ram
|
|
func LoadLDTK(dir string) *ldtkgo.Project {
|
|
fd, err := assetfs.Open("levels/openquell.ldtk")
|
|
if err != nil {
|
|
log.Fatalf("failed to open LDTK file levels/openquell.ldtk: %s", err)
|
|
}
|
|
defer fd.Close()
|
|
|
|
fileinfo, err := fd.Stat()
|
|
if err != nil {
|
|
log.Fatalf("failed to stat() LDTK file levels/openquell.ldtk: %s", err)
|
|
}
|
|
|
|
filesize := fileinfo.Size()
|
|
buffer := make([]byte, filesize)
|
|
|
|
_, err = fd.Read(buffer)
|
|
if err != nil {
|
|
log.Fatalf("failed to read bytes from LDTK file levels/openquell.ldtk: %s", err)
|
|
}
|
|
|
|
project, err := ldtkgo.Read(buffer)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// do some sanity checks
|
|
properties := []string{"minmoves", "background", "level", "description"}
|
|
need := len(properties)
|
|
|
|
for idx, level := range project.Levels {
|
|
have := 0
|
|
|
|
for _, property := range level.Properties {
|
|
if util.Contains(properties, property.Identifier) {
|
|
have++
|
|
}
|
|
}
|
|
|
|
if have != need {
|
|
log.Fatalf("level definition for level %d (%s) invalid: %d missing properties\n required: %s",
|
|
idx, level.Identifier, need-have, strings.Join(properties, ", "),
|
|
)
|
|
}
|
|
}
|
|
|
|
return project
|
|
}
|