diff --git a/artwork/sprites/flat_48x24/food/food.png b/artwork/sprites/flat_48x24/food/food.png new file mode 100644 index 0000000..276970a Binary files /dev/null and b/artwork/sprites/flat_48x24/food/food.png differ diff --git a/artwork/sprites/flat_48x24/food/food.xcf b/artwork/sprites/flat_48x24/food/food.xcf new file mode 100644 index 0000000..8457da0 Binary files /dev/null and b/artwork/sprites/flat_48x24/food/food.xcf differ diff --git a/artwork/sprites/flat_48x24/stones/stones.png b/artwork/sprites/flat_48x24/stones/stones.png new file mode 100644 index 0000000..cce5da0 Binary files /dev/null and b/artwork/sprites/flat_48x24/stones/stones.png differ diff --git a/artwork/sprites/flat_48x24/stones/stones.xcf b/artwork/sprites/flat_48x24/stones/stones.xcf new file mode 100644 index 0000000..022c461 Binary files /dev/null and b/artwork/sprites/flat_48x24/stones/stones.xcf differ diff --git a/artwork/sprites/flat_48x24/wood/wood.png b/artwork/sprites/flat_48x24/wood/wood.png new file mode 100644 index 0000000..238ffc9 Binary files /dev/null and b/artwork/sprites/flat_48x24/wood/wood.png differ diff --git a/artwork/sprites/flat_48x24/wood/wood.xcf b/artwork/sprites/flat_48x24/wood/wood.xcf new file mode 100644 index 0000000..83a96bb Binary files /dev/null and b/artwork/sprites/flat_48x24/wood/wood.xcf differ diff --git a/assets/sprites/flat_48x24.json b/assets/sprites/flat_48x24.json index 991c5fc..ed40db5 100644 --- a/assets/sprites/flat_48x24.json +++ b/assets/sprites/flat_48x24.json @@ -3,113 +3,134 @@ "SpriteHeight": 24, "Sprites": [ { - "Name": "path", + "Name": "food", "Index": 0, "Height": 0, "YOffset": 0, + "MultiTile": false + }, + { + "Name": "path", + "Index": 1, + "Height": 0, + "YOffset": 0, "MultiTile": true }, { "Name": "path_1", - "Index": 1, + "Index": 2, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_2", - "Index": 2, + "Index": 3, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_3", - "Index": 3, + "Index": 4, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_4", - "Index": 4, + "Index": 5, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_5", - "Index": 5, + "Index": 6, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_6", - "Index": 6, + "Index": 7, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_7", - "Index": 7, + "Index": 8, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_8", - "Index": 8, + "Index": 9, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_9", - "Index": 9, + "Index": 10, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_10", - "Index": 10, + "Index": 11, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_11", - "Index": 11, + "Index": 12, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_12", - "Index": 12, + "Index": 13, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_13", - "Index": 13, + "Index": 14, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_14", - "Index": 14, + "Index": 15, "Height": 0, "YOffset": 0, "MultiTile": false }, { "Name": "path_15", - "Index": 15, + "Index": 16, + "Height": 0, + "YOffset": 0, + "MultiTile": false + }, + { + "Name": "stones", + "Index": 17, + "Height": 0, + "YOffset": 0, + "MultiTile": false + }, + { + "Name": "wood", + "Index": 18, "Height": 0, "YOffset": 0, "MultiTile": false diff --git a/assets/sprites/flat_48x24.png b/assets/sprites/flat_48x24.png index f8aeb09..4a95086 100644 Binary files a/assets/sprites/flat_48x24.png and b/assets/sprites/flat_48x24.png differ diff --git a/cmd/sprites/main.go b/cmd/sprites/main.go index 77c2e91..7c30d59 100644 --- a/cmd/sprites/main.go +++ b/cmd/sprites/main.go @@ -43,8 +43,9 @@ var multiTileOrder = [16]int{ func main() { dirs := extractFiles() + allNames := map[string]bool{} for _, dir := range dirs { - processDirectory(dir) + processDirectory(dir, allNames) } } @@ -53,7 +54,7 @@ type ImagePair struct { Base image.Image } -func processDirectory(info dirInfo) { +func processDirectory(info dirInfo, names map[string]bool) { fmt.Printf("Processing %s (%d images)\n", info.Directory, len(info.Files)) if len(info.Files) == 0 { @@ -115,6 +116,11 @@ func processDirectory(info dirInfo) { if i > 0 { name = fmt.Sprintf("%s_%d", name, i) } + if _, ok := names[name]; ok { + log.Fatalf("duplicate sprite name: %s", name) + } + names[name] = true + subInfo := util.SpriteInfo{ Name: name, Index: index, @@ -131,6 +137,10 @@ func processDirectory(info dirInfo) { if sprite.Bounds().Dx() != info.Width || sprite.Bounds().Dy() != info.Height { log.Fatalf("unexpected tile size in %s: got %dx%d", file, sprite.Bounds().Dx(), sprite.Bounds().Dy()) } + if _, ok := names[baseName]; ok { + log.Fatalf("duplicate sprite name: %s", baseName) + } + names[baseName] = true images = append(images, ImagePair{sprite, nil}) infos = append(infos, spriteInfo) diff --git a/game/comp/components.go b/game/comp/components.go index 675b94e..9034cd7 100644 --- a/game/comp/components.go +++ b/game/comp/components.go @@ -25,3 +25,8 @@ type Consumption struct { Amount int Countdown int } + +type ProductionMarker struct { + StartTick int64 + Resource resource.Resource +} diff --git a/game/render/markers.go b/game/render/markers.go new file mode 100644 index 0000000..aab75cb --- /dev/null +++ b/game/render/markers.go @@ -0,0 +1,96 @@ +package render + +import ( + "image" + + "github.com/hajimehoshi/ebiten/v2" + ares "github.com/mlange-42/arche-model/resource" + "github.com/mlange-42/arche/ecs" + "github.com/mlange-42/arche/generic" + "github.com/mlange-42/tiny-world/game/comp" + "github.com/mlange-42/tiny-world/game/res" + "github.com/mlange-42/tiny-world/game/resource" +) + +// Markers is a system to render production markers. +type Markers struct { + MinOffset int + MaxOffset int + Duration int + + time generic.Resource[ares.Tick] + screen generic.Resource[res.EbitenImage] + sprites generic.Resource[res.Sprites] + view generic.Resource[res.View] + + filter generic.Filter2[comp.Tile, comp.ProductionMarker] + + resources [resource.EndResources]int +} + +// InitializeUI the system +func (s *Markers) InitializeUI(world *ecs.World) { + s.time = generic.NewResource[ares.Tick](world) + s.screen = generic.NewResource[res.EbitenImage](world) + s.sprites = generic.NewResource[res.Sprites](world) + s.view = generic.NewResource[res.View](world) + + s.filter = *generic.NewFilter2[comp.Tile, comp.ProductionMarker]() + + sprites := s.sprites.Get() + for i := resource.Resource(0); i < resource.EndResources; i++ { + s.resources[i] = sprites.GetIndex(resource.Properties[i].Name) + } +} + +// UpdateUI the system +func (s *Markers) UpdateUI(world *ecs.World) { + tick := s.time.Get().Tick + sprites := s.sprites.Get() + view := s.view.Get() + canvas := s.screen.Get() + img := canvas.Image + + off := view.Offset() + bounds := view.Bounds(canvas.Width, canvas.Height) + + op := ebiten.DrawImageOptions{} + op.Blend = ebiten.BlendSourceOver + if view.Zoom < 1 { + op.Filter = ebiten.FilterLinear + } + + halfWidth := view.TileWidth / 2 + + drawCursor := func(point *image.Point, cursor int) { + sp, info := sprites.Get(cursor) + h := sp.Bounds().Dy() - view.TileHeight + + op.GeoM.Reset() + op.GeoM.Scale(view.Zoom, view.Zoom) + op.GeoM.Translate( + float64(point.X-halfWidth)*view.Zoom-float64(off.X), + float64(point.Y-h-info.YOffset)*view.Zoom-float64(off.Y), + ) + img.DrawImage(sp, &op) + } + + query := s.filter.Query(world) + for query.Next() { + tile, mark := query.Get() + point := view.TileToGlobal(tile.X, tile.Y) + if !point.In(bounds) { + continue + } + passed := tick - mark.StartTick + off := s.MinOffset + (s.MaxOffset-s.MinOffset)*int(passed)/s.Duration + point.Y -= off + drawCursor(&point, s.resources[mark.Resource]) + } +} + +// PostUpdateUI the system +func (s *Markers) PostUpdateUI(world *ecs.World) {} + +// FinalizeUI the system +func (s *Markers) FinalizeUI(world *ecs.World) {} diff --git a/game/sys/do_production.go b/game/sys/do_production.go index 97f10f9..d283e49 100644 --- a/game/sys/do_production.go +++ b/game/sys/do_production.go @@ -6,6 +6,7 @@ import ( "github.com/mlange-42/arche/generic" "github.com/mlange-42/tiny-world/game/comp" "github.com/mlange-42/tiny-world/game/res" + "github.com/mlange-42/tiny-world/game/resource" ) // DoProduction system. @@ -14,7 +15,9 @@ type DoProduction struct { update generic.Resource[res.UpdateInterval] stock generic.Resource[res.Stock] - filter generic.Filter2[comp.UpdateTick, comp.Production] + filter generic.Filter3[comp.Tile, comp.UpdateTick, comp.Production] + markerBuilder generic.Map2[comp.Tile, comp.ProductionMarker] + toCreate []markerEntry } // Initialize the system @@ -23,7 +26,8 @@ func (s *DoProduction) Initialize(world *ecs.World) { s.update = generic.NewResource[res.UpdateInterval](world) s.stock = generic.NewResource[res.Stock](world) - s.filter = *generic.NewFilter2[comp.UpdateTick, comp.Production]() + s.filter = *generic.NewFilter3[comp.Tile, comp.UpdateTick, comp.Production]() + s.markerBuilder = generic.NewMap2[comp.Tile, comp.ProductionMarker](world) } // Update the system @@ -35,7 +39,7 @@ func (s *DoProduction) Update(world *ecs.World) { query := s.filter.Query(world) for query.Next() { - up, pr := query.Get() + tile, up, pr := query.Get() if up.Tick != tickMod { continue @@ -44,9 +48,23 @@ func (s *DoProduction) Update(world *ecs.World) { if pr.Countdown < 0 { pr.Countdown += update.Countdown stock.Res[pr.Type]++ + s.toCreate = append(s.toCreate, markerEntry{Tile: *tile, Resource: pr.Type}) } } + + for _, entry := range s.toCreate { + s.markerBuilder.NewWith( + &entry.Tile, + &comp.ProductionMarker{StartTick: tick, Resource: entry.Resource}, + ) + } + s.toCreate = s.toCreate[:0] } // Finalize the system func (s *DoProduction) Finalize(world *ecs.World) {} + +type markerEntry struct { + Tile comp.Tile + Resource resource.Resource +} diff --git a/game/sys/remove_markers.go b/game/sys/remove_markers.go new file mode 100644 index 0000000..5083efc --- /dev/null +++ b/game/sys/remove_markers.go @@ -0,0 +1,46 @@ +package sys + +import ( + ares "github.com/mlange-42/arche-model/resource" + "github.com/mlange-42/arche/ecs" + "github.com/mlange-42/arche/generic" + "github.com/mlange-42/tiny-world/game/comp" +) + +// RemoveMarkers system. +type RemoveMarkers struct { + MaxTime int64 + + time generic.Resource[ares.Tick] + filter generic.Filter1[comp.ProductionMarker] + + toRemove []ecs.Entity +} + +// Initialize the system +func (s *RemoveMarkers) Initialize(world *ecs.World) { + s.time = generic.NewResource[ares.Tick](world) + + s.filter = *generic.NewFilter1[comp.ProductionMarker]() +} + +// Update the system +func (s *RemoveMarkers) Update(world *ecs.World) { + tick := s.time.Get().Tick + + query := s.filter.Query(world) + for query.Next() { + mark := query.Get() + if tick > mark.StartTick+s.MaxTime { + s.toRemove = append(s.toRemove, query.Entity()) + } + } + + for _, e := range s.toRemove { + world.RemoveEntity(e) + } + s.toRemove = s.toRemove[:0] +} + +// Finalize the system +func (s *RemoveMarkers) Finalize(world *ecs.World) {} diff --git a/main.go b/main.go index ea01a3c..3789324 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,9 @@ func main() { g.Model.AddSystem(&sys.DoProduction{}) g.Model.AddSystem(&sys.DoConsumption{}) g.Model.AddSystem(&sys.UpdateStats{}) + g.Model.AddSystem(&sys.RemoveMarkers{ + MaxTime: 180, + }) g.Model.AddSystem(&sys.Build{ AllowStroke: true, @@ -85,6 +88,11 @@ func main() { g.Model.AddUISystem(&render.CenterView{}) g.Model.AddUISystem(&render.Terrain{}) + g.Model.AddUISystem(&render.Markers{ + MinOffset: view.TileHeight * 2, + MaxOffset: 250, + Duration: 180, + }) g.Model.AddUISystem(&render.UI{}) // =========== Run ===========