package grid import ( "image" "log" "openquell/assets" "openquell/components" "openquell/config" "openquell/observers" "github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/generic" ) type Grid struct { World *ecs.World Width int Height int Size int Tilesize int TilesX, TilesY int Map map[image.Point]*assets.Tile } // FIXME: put component addition into extra callable function, to be called // by game.go in a level-loop. Also remove components of previous level func NewGrid(world *ecs.World, tilesize, width, height int, mapslice map[image.Point]*assets.Tile) *Grid { // we use this to turn our tiles into iterable entities, used for // collision detection, transformation and other things playermapper := generic.NewMap4[ components.Position, components.Velocity, components.Renderable, components.Player](world) solidmapper := generic.NewMap4[ components.Position, components.Renderable, components.Tilish, components.Solid](world) emptymapper := generic.NewMap2[components.Position, components.Tilish](world) colmapper := generic.NewMap3[ components.Position, components.Renderable, components.Collectible](world) obsmapper := generic.NewMap4[ components.Position, components.Velocity, components.Renderable, components.Obstacle](world) transmapper := generic.NewMap3[ components.Position, components.Renderable, components.Transient](world) destructiblemapper := generic.NewMap3[ components.Position, components.Renderable, components.Destroyable](world) switchmapper := generic.NewMap4[ components.Position, components.Renderable, components.Bond, components.Switch](world) doormapper := generic.NewMap4[ components.Position, components.Renderable, components.Bond, components.Door](world) var pos *components.Position var vel *components.Velocity var render *components.Renderable var transient *components.Transient var player *components.Player var destroyable *components.Destroyable var switcher *components.Switch var door *components.Door var bond *components.Bond playerID := ecs.ComponentID[components.Player](world) obstacleID := ecs.ComponentID[components.Obstacle](world) observer := observers.GetGameObserver(world) switches := []ecs.Entity{} doors := []ecs.Entity{} for point, tile := range mapslice { switch tile.Renderable { case true: switch { case tile.Solid: entity := solidmapper.New() pos, render, _, _ = solidmapper.Get(entity) case tile.Player: entity := playermapper.New() pos, vel, render, player = playermapper.Get(entity) observer.AddEntity(entity, playerID) vel.Speed = config.PLAYERSPEED player.IsPrimary = tile.IsPrimary player.Sprites = tile.Tiles player.LoopPos = &components.Position{Cellsize: tilesize} case tile.Collectible: entity := colmapper.New() pos, render, _ = colmapper.Get(entity) case tile.Obstacle: entity := obsmapper.New() pos, vel, render, _ = obsmapper.Get(entity) vel.Direction = tile.Direction vel.PointingAt = tile.Direction vel.Speed = config.PLAYERSPEED observer.AddEntity(entity, obstacleID) case tile.Transient: entity := transmapper.New() pos, render, transient = transmapper.Get(entity) transient.Wall = tile.ToggleSprite case tile.Destroyable: entity := destructiblemapper.New() pos, render, destroyable = destructiblemapper.Get(entity) destroyable.Sprites = tile.Tiles render.DamageImage = tile.Alpha render.Shader = tile.Shader render.Damaged = 0 case tile.Switch: entity := switchmapper.New() pos, render, _, switcher = switchmapper.Get(entity) switcher.CloseSprite = tile.Sprite switcher.OpenSprite = tile.ToggleSprite switcher.Ref = tile.Ref switches = append(switches, entity) case tile.Door: entity := doormapper.New() pos, render, _, door = doormapper.Get(entity) door.CloseSprite = tile.Sprite door.OpenSprite = tile.ToggleSprite door.Id = tile.Id doors = append(doors, entity) default: log.Fatalln("unsupported tile type encountered") } render.Image = tile.Sprite render.Pos = pos //if tile.AnimateOnDestruct { if tile.AnimationTrigger != "" { // FIXME: be more generic, use LDTK enum render.Animate.Tiles = tile.AnimationSpriteSheet.Sprites render.Animate.Width = tile.AnimationSpriteSheet.Width render.Animate.Height = tile.AnimationSpriteSheet.Height render.Animate.Trigger = tile.AnimationTrigger } default: // empty cell, this is where the player[s] move. No // sprite required since every level has a background // image. entity := emptymapper.New() pos, _ = emptymapper.Get(entity) } // however, every tile has a position pos.Update(point.X*tilesize, point.Y*tilesize, tilesize) } // check for switch->door references for _, switchentity := range switches { _, _, bond, switcher = switchmapper.Get(switchentity) if switcher.Ref != "" { // this switch has a reference for _, doorentity := range doors { _, _, _, door = doormapper.Get(doorentity) if door.Id == switcher.Ref { // the switch reference matches the door id, relate the two bond.Ref = switcher.Ref relID := ecs.ComponentID[components.Bond](world) world.Relations().Set(switchentity, relID, doorentity) } } } } return &Grid{ Size: len(mapslice), Tilesize: tilesize, Width: width, Height: height, Map: mapslice, World: world, TilesX: width / tilesize, TilesY: height / tilesize, } } // return the tile the moving object would end up on if indeed moving func (grid *Grid) GetTile( position *components.Position, velocity *components.Velocity) *assets.Tile { newpoint := image.Point{ int(position.X+velocity.Data.X) / grid.Tilesize, int(position.Y+velocity.Data.Y) / grid.Tilesize, } tile := grid.Map[newpoint] return tile } func (grid *Grid) RemoveTile(point image.Point) { delete(grid.Map, point) } func (grid *Grid) SetFloorTile(point image.Point) { grid.Map[point] = assets.Tiles["floor"] } func (grid *Grid) SetSolidTile(tile *assets.Tile, point image.Point) { grid.Map[point] = tile }