package game import ( "image" "log" "log/slog" "openquell/assets" "openquell/components" "openquell/grid" "openquell/observers" "openquell/systems" "openquell/util" "strings" "github.com/hajimehoshi/ebiten/v2" "github.com/mlange-42/arche/ecs" ) type Map map[image.Point]*assets.Tile type BackupMap map[image.Point]assets.Tile type Level struct { Cellsize, Width, Height int World *ecs.World Name string Description string Mapslice Map BackupMapslice Map GridContainer *grid.GridContainer Systems []systems.System Grid *grid.Grid } func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { systemlist := []systems.System{} gridcontainer := &grid.GridContainer{} systemlist = append(systemlist, systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize, plan.Background)) systemlist = append(systemlist, systems.NewCollectibleSystem(game.World)) systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer)) systemlist = append(systemlist, systems.NewPlayerSystem(game.World, gridcontainer, game.ScreenWidth, game.ScreenHeight)) systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize)) systemlist = append(systemlist, systems.NewTransientSystem(game.World, gridcontainer)) systemlist = append(systemlist, systems.NewDestroyableSystem(game.World, gridcontainer)) systemlist = append(systemlist, systems.NewHudSystem(game.World, plan)) mapslice, backupmap := LevelToSlice(game, plan, cellsize) return &Level{ Mapslice: mapslice, BackupMapslice: backupmap, Cellsize: cellsize, World: game.World, Width: game.ScreenWidth, Height: game.ScreenHeight, Description: plan.Description, Name: plan.Name, GridContainer: gridcontainer, Systems: systemlist, } } func (level *Level) Update() { for _, sys := range level.Systems { sys.Update() } } func (level *Level) Draw(screen *ebiten.Image) { for _, sys := range level.Systems { sys.Draw(screen) } } func (level *Level) RestoreMap() { for point, tile := range level.BackupMapslice { level.Mapslice[point] = tile.Clone() } } func (level *Level) SetupGrid(game *Game) { // generic variant does not work here: // selector := generic.NewFilter1[components.Position]() // level.World.Batch().RemoveEntities(selector) // missing argument in conversion to generic.Filter1[components.Position] // erase all entities of previous level, if any posID := ecs.ComponentID[components.Position](level.World) selector := ecs.All(posID) level.World.Batch().RemoveEntities(selector) // get rid of any players on PlayerObserver. FIXME: remove them in grid.NewGrid()? observer := observers.GetGameObserver(level.World) observer.RemoveEntities() // get rid of possibly manipulated map level.RestoreMap() // setup world slog.Debug("new grid?") level.GridContainer.SetGrid( grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice)) } // parses a RawLevel and generates a mapslice from it, which is being used as grid func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) (Map, Map) { size := game.ScreenWidth * game.ScreenHeight mapslice := make(Map, size) backupmap := make(Map, size) for y, line := range strings.Split(string(level.Data), "\n") { if len(line) != game.ScreenWidth/tilesize && y < game.ScreenHeight/tilesize { log.Fatalf("line %d doesn't contain %d tiles, but %d", y, game.ScreenWidth/tilesize, len(line)) } for x, char := range line { if !util.Exists(assets.Tiles, byte(char)) { log.Fatalf("unregistered tile type %c encountered", char) } tile := assets.Tiles[byte(char)] mapslice[image.Point{x, y}] = tile backupmap[image.Point{x, y}] = tile.Clone() } } return mapslice, backupmap }