218 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package game
 | |
| 
 | |
| import (
 | |
| 	"image"
 | |
| 	"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"
 | |
| 	"github.com/solarlune/ldtkgo"
 | |
| )
 | |
| 
 | |
| 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
 | |
| 	Number                  int
 | |
| 	Mapslice                Map
 | |
| 	BackupMapslice          Map
 | |
| 	GridContainer           *grid.GridContainer
 | |
| 	Systems                 []systems.System
 | |
| 	Grid                    *grid.Grid
 | |
| }
 | |
| 
 | |
| func NewLevel(game *Game, cellsize int, plan *ldtkgo.Level) *Level {
 | |
| 	systemlist := []systems.System{}
 | |
| 
 | |
| 	gridcontainer := &grid.GridContainer{}
 | |
| 	bgimage := util.GetBGImage(plan)
 | |
| 
 | |
| 	systemlist = append(systemlist,
 | |
| 		systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize,
 | |
| 			bgimage))
 | |
| 
 | |
| 	systemlist = append(systemlist, systems.NewCollectibleSystem(game.World, cellsize))
 | |
| 
 | |
| 	systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer))
 | |
| 
 | |
| 	systemlist = append(systemlist, systems.NewPairSystem(game.World, gridcontainer))
 | |
| 
 | |
| 	systemlist = append(systemlist,
 | |
| 		systems.NewPlayerSystem(game.World, gridcontainer, game.ScreenWidth, game.ScreenHeight))
 | |
| 
 | |
| 	systemlist = append(systemlist, systems.NewAnimationSystem(game.World, game.Cellsize))
 | |
| 
 | |
| 	systemlist = append(systemlist, systems.NewTransientSystem(game.World, gridcontainer))
 | |
| 
 | |
| 	systemlist = append(systemlist, systems.NewDestroyableSystem(game.World, gridcontainer,
 | |
| 		game.Cellsize))
 | |
| 
 | |
| 	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.PropertyByIdentifier("description").AsString(),
 | |
| 		Number:         plan.PropertyByIdentifier("level").AsInt(),
 | |
| 		Name:           strings.ReplaceAll(plan.Identifier, "_", " "),
 | |
| 		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))
 | |
| }
 | |
| 
 | |
| func NewMapSlice(game *Game, tilesize int) Map {
 | |
| 	size := game.ScreenWidth * game.ScreenHeight
 | |
| 	mapslice := make(Map, size)
 | |
| 
 | |
| 	for y := 0; y < game.ScreenHeight; y += 32 {
 | |
| 		for x := 0; x < game.ScreenWidth; x += 32 {
 | |
| 			mapslice[image.Point{x / tilesize, y / tilesize}] = assets.Tiles["floor"]
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return mapslice
 | |
| }
 | |
| 
 | |
| // parses a RawLevel and generates a mapslice from it, which is being used as grid
 | |
| func LevelToSlice(game *Game, level *ldtkgo.Level, tilesize int) (Map, Map) {
 | |
| 	mapslice := NewMapSlice(game, tilesize)
 | |
| 	backupmap := NewMapSlice(game, tilesize)
 | |
| 
 | |
| 	for _, layer := range level.Layers {
 | |
| 		switch layer.Type {
 | |
| 		case ldtkgo.LayerTypeTile:
 | |
| 			// load tile from LDTK tile layer, use sprites from associated map.
 | |
| 
 | |
| 			if tiles := layer.AllTiles(); len(tiles) > 0 {
 | |
| 				for _, tileData := range tiles {
 | |
| 					// Subimage the Tile  from the already loaded map,
 | |
| 					//  but referenced  from  LDTK file,  that way  we
 | |
| 					// could use multiple tileset images
 | |
| 					tile := assets.Tiles["default"]
 | |
| 
 | |
| 					// FIXME: load from LDTK file
 | |
| 					tile.Sprite = assets.Assets["primarymap"].SubImage(
 | |
| 						image.Rect(tileData.Src[0],
 | |
| 							tileData.Src[1],
 | |
| 							tileData.Src[0]+layer.GridSize,
 | |
| 							tileData.Src[1]+layer.GridSize)).(*ebiten.Image)
 | |
| 
 | |
| 					point := image.Point{
 | |
| 						tileData.Position[0] / tilesize,
 | |
| 						tileData.Position[1] / tilesize}
 | |
| 
 | |
| 					mapslice[point] = tile
 | |
| 					backupmap[point] = tile.Clone()
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 		case ldtkgo.LayerTypeEntity:
 | |
| 			// load mobile tiles (they call them entities) using static map map.png.
 | |
| 			tileset := assets.Assets["entitymap"]
 | |
| 
 | |
| 			for _, entity := range layer.Entities {
 | |
| 				if entity.TileRect != nil {
 | |
| 					tile := assets.Tiles[entity.Identifier]
 | |
| 					tile.Id = entity.IID
 | |
| 
 | |
| 					toggleRect := util.GetPropertyToggleTile(entity)
 | |
| 					if toggleRect != nil {
 | |
| 						tile.ToggleSprite = tileset.SubImage(
 | |
| 							image.Rect(toggleRect.X, toggleRect.Y,
 | |
| 								toggleRect.X+toggleRect.W,
 | |
| 								toggleRect.Y+toggleRect.H)).(*ebiten.Image)
 | |
| 
 | |
| 					}
 | |
| 
 | |
| 					tile.Ref = util.GetPropertyRef(entity)
 | |
| 					if tile.Transient {
 | |
| 						slog.Debug("LOAD TILE", "tileref",
 | |
| 							tile.Ref, "tileid", tile.Id,
 | |
| 							"name", entity.Identifier,
 | |
| 							"isswitch", tile.Switch,
 | |
| 							"isdoor", tile.Door,
 | |
| 							"togglerect", toggleRect,
 | |
| 							"tilerect", entity.TileRect,
 | |
| 						)
 | |
| 
 | |
| 					}
 | |
| 					tileRect := entity.TileRect
 | |
| 
 | |
| 					tile.Sprite = tileset.SubImage(
 | |
| 						image.Rect(tileRect.X, tileRect.Y,
 | |
| 							tileRect.X+tileRect.W,
 | |
| 							tileRect.Y+tileRect.H)).(*ebiten.Image)
 | |
| 
 | |
| 					point := image.Point{
 | |
| 						entity.Position[0] / tilesize,
 | |
| 						entity.Position[1] / tilesize}
 | |
| 
 | |
| 					mapslice[point] = tile
 | |
| 					backupmap[point] = tile.Clone()
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return mapslice, backupmap
 | |
| }
 |