36 Commits

Author SHA1 Message Date
dc2574643c fixed 2024-03-27 18:39:04 +01:00
4c2ecc6f1f Changes:
- separated entities spriteset from primarymap, which only contains
  tiles
- fixed star obstacle
2024-03-27 18:37:41 +01:00
5b7c88a1a9 fixed hiddendoor tiles, now uses destruct shader no spriteset 2024-03-24 19:36:00 +01:00
f8c09fda76 first try to use new tiles 2024-03-22 19:02:52 +01:00
693293b8c8 added 2024-03-22 17:57:44 +01:00
51d3776abf fixed player loop detection, added more levels 2024-03-21 19:04:47 +01:00
19c9a5d502 better wasm build 2024-03-21 13:32:14 +01:00
d055c9e913 Merge pull request 'separate-collision-checker' (#1) from separate-collision-checker into master
Reviewed-on: #1
2024-03-21 13:31:03 +01:00
52c4cb4e66 bump 2024-03-21 13:28:44 +01:00
e93c08f81f fixes:
- using 1 func for grid collision checking with func arguments in
  player and obstacle systems.
- moving obstacles now kill player if business ends points toward him
  on contact
- added retry in popup menu
2024-03-21 13:25:06 +01:00
1d16fcb73f fixes:
- fixed bumpedge test for obstacles
- fixed obstacle crash when going to the south or east
- added wasm builder
2024-03-21 09:38:47 +01:00
ae0e3a0676 added mouse menu to player hud (events in level_scene), fix level 4 2024-03-18 19:34:57 +01:00
daa5e41551 center description 2024-03-17 16:30:11 +01:00
35057ec8a6 added back button 2024-03-17 16:29:56 +01:00
449a75aa7d added more levels 2024-03-17 16:29:41 +01:00
db228094eb fixed level select widget size 2024-03-16 23:11:22 +01:00
927233aafb +ui bug 2024-03-16 18:50:24 +01:00
b9ac1c7995 added one more level, rm comments 2024-03-16 18:49:28 +01:00
2084150456 fixed last level oos bug, added more levels and entities 2024-03-15 20:08:23 +01:00
1d9164d140 finaly got ldtkgo to work! 2024-03-11 18:31:01 +01:00
af19ccb833 shifted from proprietary ascii rawlevel to LDTK (using ldtkgo@master) 2024-03-10 13:05:31 +01:00
a2ed7782b2 added game font rendering using etxt to player hud, fixed current level 2024-03-01 18:10:09 +01:00
ae8058e052 added final menu after last level 2024-03-01 14:08:35 +01:00
6f85a90b58 fixed slow grid drawing, which also fixed transient tile quirk 2024-03-01 10:55:14 +01:00
1c0f3d19d0 added MinMoves to lvl files, added score system in game observer 2024-02-28 19:58:05 +01:00
ebaeb51f68 started implementing score system 2024-02-28 13:15:45 +01:00
707281212a added player loop detection, loose level if last player loops 2024-02-27 18:20:00 +01:00
5d2475d525 two players fuse now if they collide 2024-02-27 17:28:18 +01:00
ac88a74bd7 turn player into primary if only one left 2024-02-27 16:52:20 +01:00
18be0ebe38 added hud system, turned all observers into one central observer 2024-02-27 14:45:23 +01:00
451b66a53d obstacle collision fixes, player alive checks, player remove
workaround, see TODO
2024-02-26 19:07:35 +01:00
0ca5d8f4a0 more TODO 2024-02-26 14:43:03 +01:00
8f0fb746c0 fix collision for collectibles 2024-02-26 13:55:54 +01:00
ffaa5e6129 fixed higher speed collisions, removed Speed comp, put into Velocity 2024-02-26 12:56:12 +01:00
2e3acf4ec4 fix iterating on dead entities after player dies 2024-02-25 14:19:21 +01:00
b0a8060d5b added hidden doors/destroyable walls, fixed game reloading (tile clone) 2024-02-25 14:05:44 +01:00
184 changed files with 8385 additions and 822 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
openquell openquell*
tmp

View File

@@ -1,3 +1,12 @@
version = $(shell egrep "version string = " config/static.go | cut -d'"' -f 2)
BRANCH = $(shell git branch --show-current)
COMMIT = $(shell git rev-parse --short=8 HEAD)
BUILD = $(shell date +%Y.%m.%d.%H%M%S)
#VERSION := $(if $(filter $(BRANCH), development),$(version)-$(BRANCH)-$(COMMIT)-$(BUILD),$(version))
VERSION := $(version)-$(BRANCH)-$(COMMIT)-$(BUILD)
SHORTVERSION := $(version)-$(BRANCH)-$(COMMIT)
LDFLAGS := -ldflags "-X 'openquell/config.VERSION=$(VERSION)'"
all: clean build all: clean build
@echo ok @echo ok
@@ -5,8 +14,16 @@ clean:
rm -f openquell rm -f openquell
build: build:
go build go build -ldflags "-X 'openquell/config.VERSION=$(VERSION)'"
buildwasm:
env GOOS=js GOARCH=wasm go build -o openquell.wasm $(LDFLAGS) .
zipwasm:
zip -r openquell-$(SHORTVERSION).zip index.html openquell.wasm wasm_exec.js
wasm: buildwasm zipwasm
@ls -l openquell-$(SHORTVERSION).zip
test: test:
@echo 1 @echo $(VERSION)

60
TODO.md
View File

@@ -1,35 +1,51 @@
## Levels: ## Levels:
- use first line as background image name
- ignore comments in lvl files - ignore comments in lvl files
- check when sphere bounces from one end to the other endlessly w/o
any solids in between. this is a game lock and equals game over
- Start the game end timer and add a score FIXME: put the actual max
reachable score into the level definition along with the minimum
steps required to reach it, also add a step counter
- Grid Observer: - Grid Observer:
https://github.com/mlange-42/arche/issues/374 https://github.com/mlange-42/arche/issues/374
- Add shaders for animation (player destruction etc)
Screenshot: - Start New game starts with last played level, better add a Resume
Game menu item for this and use Start New always for level 1.
Yes, since *ebiten.Image implements the standard image.Image interface - for finding caller:
I just made a screenshot example (in Draw() function): pc := make([]uintptr, 10)
if inpututil.IsKeyJustPressed(ebiten.KeyS) { n := runtime.Callers(0, pc)
f, err := os.Create("screenshot.png") pc = pc[:n]
if err != nil { fs := runtime.CallersFrames(pc)
log.Fatal("can't create file: ", err) source, _ := fs.Next()
} source, _ = fs.Next()
png.Encode(f, screen) source, _ = fs.Next()
}
slog.Debug("get observer", "minmoves", observer.LevelScore,
"file", source.File, "line", source.Line)
- Add some final message when the player reaches the last level, start - Turn menu button in hud_system (events in level_scene!) into ebitenui button
from scratch or a message to buy me some beer, whatever
- Calculate obstacle collision the same as solid collision with future - Obstacle don't stop at collectibles
velocity and included snap in so that the player ends up right on
the edge of the obstacle and not inside as it is now
- Check player-player collisions! - Player can collect collectible hidden under obstacle (should be
fixed when above is fixed)
- Remove Sprite from Tile{}, not used anymore
## Collider Rework [abandoned: see branch collider-system, fails]
- do not use the map anymore for collision detection
- central collision_system
- add Collider component with callback funcs to call on certain events
- callback types:
- rect intersect (== future collision)
- collision resolve (set new position)
- pass over foreign rect (to e.g. change sprite while flying over sprite)
- pass over done (switch sprite)
- check for all moving objects against non-moving ones like moving
obstacle, player, bullet, laser
- check if executing callbacks within query loop is allowed
- callback function needs to be able to modify other components, so
possibly use observers for them, e.g.:
Collider.IntersectResolve => func(newpos) {player.pos = newpos; player.vel = stop}
- in the end it must be possible to add new entities without the need
to write a collision check for them, but have collision detection anyway!

View File

@@ -1,10 +1,11 @@
Description: test obstacles Description: open the door
MinMoves: 2
Background: background-lila Background: background-lila
####### #######
# # #o #
# t # # t #
#> S <# #> S <#
# # # #

View File

@@ -1,14 +1,15 @@
Description: test obstacles Description: test obstacles
MinMoves: 14
Background: background-lila Background: background-lila
############# #############
# # # t #
#> S # # #> S # #
# ># ># # ># ^#
#< # # #< #W#####
# v# <# # o v# o#
############# #############

View File

@@ -1,4 +1,5 @@
Description: find the fruit Description: collect all yellow dots
MinMoves: 7
Background: background-lila Background: background-lila

View File

@@ -1,17 +1,18 @@
Description: win Description: some enemies can be moved
MinMoves: 5
Background: background-lila Background: background-lila
######## ########
#v o # # o <o#
# S<# # #v S # #
# #### # # #### #
# #### # # #### #
# # # #
#> ^# # ^#
######## ########

View File

@@ -1,16 +1,17 @@
Description: use multiple players Description: the more players the better
MinMoves: 4
Background: background-lila Background: background-lila
########## ########W#
# v# # v#
# # # #
#S s# #S s#
# # ############
#^ # # o #
########## ######## #

View File

@@ -0,0 +1,18 @@
Description: don't fall off the world
MinMoves: 3
Background: background-lila
W
S # o
o
#

5835
assets/levels/openquell.ldtk Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,17 @@
package assets package assets
import ( import (
"bufio"
_ "image/png" _ "image/png"
"io/fs"
"log" "log"
"log/slog"
"openquell/config" "openquell/config"
"openquell/util" "openquell/util"
"os"
"path/filepath"
"sort"
"strings" "strings"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/solarlune/ldtkgo"
) )
var Levels = LoadLevels("levels") var Project = LoadLDTK("levels")
var Tiles = InitTiles() var Tiles = InitTiles()
// Tile: contains image, identifier (as used in level data) and // Tile: contains image, identifier (as used in level data) and
@@ -25,18 +20,46 @@ type Tile struct {
Id byte Id byte
Sprite *ebiten.Image Sprite *ebiten.Image
Class string Class string
Solid bool Solid bool // wall brick
Player bool Player bool // player sphere
IsPrimary bool IsPrimary bool // primary player sphere
Renderable bool Renderable bool // visible, has sprite
Velocity bool Velocity bool // movable
Collectible bool Collectible bool // collectible, vanishes once collected
Transient bool Transient bool // turns into brick wall when traversed
Particle int // -1=unused, 0-3 = show image of slice Destroyable bool // turns into empty floor when bumped into twice
Tiles []*ebiten.Image Particle int // -1=unused, 0-3 = show image of slice
TileNames []string // same thing, only the names Tiles []*ebiten.Image // has N sprites
Obstacle bool TileNames []string // same thing, only the names
Direction int // obstacles Obstacle bool // is an obstacle/enemy
Direction int // obstacle business end shows into this direction
Shader *ebiten.Shader
Alpha *ebiten.Image
}
func (tile *Tile) Clone() *Tile {
newtile := &Tile{
Id: tile.Id,
Sprite: tile.Sprite,
Class: tile.Class,
Solid: tile.Solid,
Player: tile.Player,
IsPrimary: tile.IsPrimary,
Renderable: tile.Renderable,
Velocity: tile.Velocity,
Collectible: tile.Collectible,
Transient: tile.Transient,
Destroyable: tile.Destroyable,
Particle: tile.Particle,
Tiles: tile.Tiles,
TileNames: tile.TileNames,
Obstacle: tile.Obstacle,
Direction: tile.Direction,
Alpha: tile.Alpha,
Shader: tile.Shader,
}
return newtile
} }
const ( const (
@@ -128,7 +151,7 @@ func NewTileTranswall(class []string) *Tile {
} }
return &Tile{ return &Tile{
Id: '*', Id: 't',
Class: "transwall", Class: "transwall",
Solid: false, Solid: false,
Renderable: true, Renderable: true,
@@ -139,123 +162,105 @@ func NewTileTranswall(class []string) *Tile {
} }
} }
// used to map level data bytes to actual tiles func NewTileHiddenDoor(class, alpha string) *Tile {
type TileRegistry map[byte]*Tile return &Tile{
Id: 'W',
// holds a raw level spec: Class: "hiddendoor",
// Solid: false,
// Name: the name of the level file w/o the .lvl extension Renderable: true,
// Background: an image name used as game background for this level Destroyable: true,
// Description: text to display on top Sprite: Assets[class],
// Data: a level spec consisting of chars of the above mapping and spaces, e.g.: Alpha: Assets[alpha],
// #### Shader: Shaders["destruct"],
// # # }
// ####
//
// Each level data must be 20 chars wide (= 640 px width) and 15 chars
// high (=480 px height).
type RawLevel struct {
Name string
Description string
Background *ebiten.Image
Data []byte
} }
// used to map level data bytes to actual tiles
type TileRegistry map[string]*Tile
func InitTiles() TileRegistry { func InitTiles() TileRegistry {
return TileRegistry{ return TileRegistry{
' ': {Id: ' ', Class: "floor", Renderable: false}, "floor": {Id: ' ', Class: "floor", Renderable: false},
'#': NewTileBlock("block-grey32"), "default": NewTileBlock("block-greycolored"),
'B': NewTileBlock("block-orange-32"), "solidorange": NewTileBlock("block-orange-32"),
'S': NewTilePlayer(Primary), "PlayerPrimary": NewTilePlayer(Primary),
's': NewTilePlayer(Secondary), "PlayerSecondary": NewTilePlayer(Secondary),
'o': NewTileCollectible("collectible-orange"), "Collectible": NewTileCollectible("collectible-orange"),
'+': NewTileObstacle("obstacle-star", config.All), "ObstacleStar": NewTileObstacle("obstacle-star", config.All),
'^': NewTileObstacle("obstacle-north", config.North), "ObstacleNorth": NewTileObstacle("obstacle-north", config.North),
'v': NewTileObstacle("obstacle-south", config.South), "ObstacleSouth": NewTileObstacle("obstacle-south", config.South),
'<': NewTileObstacle("obstacle-west", config.West), "ObstacleWest": NewTileObstacle("obstacle-west", config.West),
'>': NewTileObstacle("obstacle-east", config.East), "ObstacleEast": NewTileObstacle("obstacle-east", config.East),
'*': NewTileParticle([]string{ "Particle": NewTileParticle([]string{
//"particle-ring-1", "particle-ring-1",
"particle-ring-2", "particle-ring-2",
"particle-ring-3", "particle-ring-3",
"particle-ring-4", "particle-ring-4",
"particle-ring-5", "particle-ring-5",
"particle-ring-6", "particle-ring-6",
}), }),
't': NewTileTranswall([]string{"transwall", "block-orange-32"}), "Transient": NewTileTranswall([]string{"transwall", "block-orange-32"}),
"HiddenDoor": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor2": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor3": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor4": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor5": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor6": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor7": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor8": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor9": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor10": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor11": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor12": NewTileHiddenDoor("block-greycolored", "damage"),
"HiddenDoor13": NewTileHiddenDoor("block-greycolored", "damage"),
} }
} }
// load levels at compile time into ram, creates a slice of raw levels // load LDTK project at compile time into ram
func LoadLevels(dir string) []RawLevel { func LoadLDTK(dir string) *ldtkgo.Project {
levels := []RawLevel{} fd, err := assetfs.Open("levels/openquell.ldtk")
// we use embed.FS to iterate over all files in ./levels/
entries, err := assetfs.ReadDir(dir)
if err != nil { if err != nil {
log.Fatalf("failed to read level dir %s: %s", dir, err) log.Fatalf("failed to open LDTK file levels/openquell.ldtk: %s", err)
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
for _, levelfile := range entries {
if levelfile.Type().IsRegular() && strings.Contains(levelfile.Name(), ".lvl") {
path := filepath.Join("assets", dir)
level := ParseRawLevel(path, levelfile)
levels = append(levels, level)
slog.Debug("loaded level", "path", path, "file", levelfile)
}
}
return levels
}
func ParseRawLevel(dir string, levelfile fs.DirEntry) RawLevel {
fd, err := os.Open(filepath.Join(dir, levelfile.Name()))
if err != nil {
log.Fatalf("failed to read level file %s: %s", levelfile.Name(), err)
} }
defer fd.Close() defer fd.Close()
name := strings.TrimSuffix(levelfile.Name(), ".lvl") fileinfo, err := fd.Stat()
des := "" if err != nil {
background := &ebiten.Image{} log.Fatalf("failed to stat() LDTK file levels/openquell.ldtk: %s", err)
data := []byte{} }
scanner := bufio.NewScanner(fd) filesize := fileinfo.Size()
for scanner.Scan() { buffer := make([]byte, filesize)
// ignore any whitespace
line := scanner.Text()
// ignore empty lines _, err = fd.Read(buffer)
if len(line) == 0 { if err != nil {
continue log.Fatalf("failed to read bytes from LDTK file levels/openquell.ldtk: %s", err)
} }
switch { project, err := ldtkgo.Read(buffer)
case strings.Contains(line, "Background:"): if err != nil {
haveit := strings.Split(line, ": ") panic(err)
if util.Exists(Assets, haveit[1]) { }
background = Assets[haveit[1]]
// 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++
} }
case strings.Contains(line, "Description:"): }
haveit := strings.Split(line, ": ")
des = haveit[1] if have != need {
default: log.Fatalf("level definition for level %d (%s) invalid: %d missing properties\n required: %s",
// all other non-empty and non-equalsign lines are idx, level.Identifier, need-have, strings.Join(properties, ", "),
// level definition matrix data, merge thes into data )
data = append(data, line+"\n"...)
} }
} }
return RawLevel{ return project
Name: name,
Data: data,
Background: background,
Description: des,
}
} }

49
assets/loader-shaders.go Normal file
View File

@@ -0,0 +1,49 @@
package assets
import (
"bytes"
"log"
"log/slog"
"path/filepath"
"strings"
"github.com/hajimehoshi/ebiten/v2"
)
type ShaderRegistry map[string]*ebiten.Shader
var Shaders = LoadShaders("shaders")
func LoadShaders(dir string) ShaderRegistry {
shaders := ShaderRegistry{}
entries, err := assetfs.ReadDir(dir)
if err != nil {
log.Fatalf("failed to read shaders dir %s: %s", dir, err)
}
for _, file := range entries {
path := filepath.Join(dir, file.Name())
fd, err := assetfs.Open(path)
if err != nil {
log.Fatalf("failed to open shader file %s: %s", file.Name(), err)
}
defer fd.Close()
name := strings.TrimSuffix(file.Name(), ".kg")
buf := new(bytes.Buffer)
buf.ReadFrom(fd)
shader, err := ebiten.NewShader([]byte(buf.Bytes()))
if err != nil {
log.Fatal(err)
}
shaders[name] = shader
slog.Debug("loaded shader asset", "path", path)
}
return shaders
}

View File

@@ -6,7 +6,6 @@ import (
_ "image/png" _ "image/png"
"log" "log"
"log/slog" "log/slog"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -16,7 +15,7 @@ import (
// Maps image name to image data // Maps image name to image data
type AssetRegistry map[string]*ebiten.Image type AssetRegistry map[string]*ebiten.Image
//go:embed levels/*.lvl sprites/*.png fonts/*.ttf //go:embed sprites/*.png fonts/*.ttf levels/*.ldtk shaders/*.kg
var assetfs embed.FS var assetfs embed.FS
var Assets = LoadImages("sprites") var Assets = LoadImages("sprites")
@@ -31,8 +30,8 @@ func LoadImages(dir string) AssetRegistry {
} }
for _, imagefile := range entries { for _, imagefile := range entries {
path := filepath.Join("assets", dir, imagefile.Name()) path := filepath.Join(dir, imagefile.Name())
fd, err := os.Open(path) fd, err := assetfs.Open(path)
if err != nil { if err != nil {
log.Fatalf("failed to open image file %s: %s", imagefile.Name(), err) log.Fatalf("failed to open image file %s: %s", imagefile.Name(), err)
} }

View File

@@ -0,0 +1,36 @@
// Copyright 2020 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ignore
//kage:unit pixels
package main
var Damaged int
const HP float = 0
func Fragment(_ vec4, texCoord vec2, _ vec4) vec4 {
wallpx := imageSrc0At(texCoord) // A pixel from the wall tile
mask := imageSrc1At(texCoord) // A pixel from the damage mask image
if Damaged == 1 && (wallpx.a != 0.0 && mask.a != 0.0) {
alpha := clamp(HP+(1.0-mask.a), 0.0, 1.0)
// Create a darker pixel if it's inside a damage mask.
return vec4(wallpx.r*alpha, wallpx.g*alpha, wallpx.b*alpha, wallpx.a)
}
return wallpx // Otherwise, leave a pixel color as is
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

BIN
assets/sprites/damage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
assets/sprites/hud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,2 @@
#!/bin/sh
montage -tile 4x0 -geometry +0+0 block* collectible-orange.png obstacle-* sphere-blue* transwall.png map.png && okular map.png

BIN
assets/sprites/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/sprites/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
assets/sprites/tilemap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

View File

@@ -7,7 +7,10 @@ import (
// virtual location, aka tile address // virtual location, aka tile address
type Renderable struct { type Renderable struct {
Image *ebiten.Image Image *ebiten.Image
DamageImage *ebiten.Image
Damaged int
Shader *ebiten.Shader
} }
type Particle struct { type Particle struct {
@@ -16,10 +19,6 @@ type Particle struct {
Tiles []*ebiten.Image Tiles []*ebiten.Image
} }
type Speed struct {
Value int
}
// only tile entities will have those // only tile entities will have those
type Tilish struct{} type Tilish struct{}
type Solid struct{} type Solid struct{}

29
components/destroyable.go Normal file
View File

@@ -0,0 +1,29 @@
package components
import (
"log"
"github.com/hajimehoshi/ebiten/v2"
)
// A hidden door in a wall. If the player bumps into it once, it shows
// damage and it vanishes the next time.
type Destroyable struct {
Activated bool
Sprites []*ebiten.Image
Current int // sprite index
}
func (door *Destroyable) GetNext() *ebiten.Image {
if len(door.Sprites) > door.Current {
door.Current++
return door.Sprites[door.Current]
}
log.Fatalf("not enough sprites in transient tile, have %d sprites, index requested: %d",
len(door.Sprites), door.Current+1,
)
return nil
}

View File

@@ -5,6 +5,9 @@ import "github.com/hajimehoshi/ebiten/v2"
type Player struct { type Player struct {
IsPrimary bool IsPrimary bool
Sprites []*ebiten.Image Sprites []*ebiten.Image
LoopPos *Position
LoopStart bool
LoopCount int
} }
func (player *Player) SwitchSprite() *ebiten.Image { func (player *Player) SwitchSprite() *ebiten.Image {

View File

@@ -34,7 +34,8 @@ func NewPosition(point image.Point, cellsize int) *Position {
func (position *Position) GetMoved(velosity *Velocity) *Position { func (position *Position) GetMoved(velosity *Velocity) *Position {
pos := &Position{} pos := &Position{}
pos.Update(position.X+velosity.Data.X, position.Y+velosity.Data.Y, position.Cellsize) pos.Update(position.X+(velosity.Data.X*velosity.Speed),
position.Y+(velosity.Data.Y*velosity.Speed), position.Cellsize)
return pos return pos
} }
@@ -70,13 +71,13 @@ func (position *Position) String() string {
) )
} }
func (position *Position) Move(velocity *Velocity, speed *Speed) { func (position *Position) Move(velocity *Velocity) {
// if velocity.Direction != 0 { // if velocity.Direction != 0 {
// slog.Debug("moving", "velocity", velocity, "speed", speed) // slog.Debug("moving", "velocity", velocity, "speed", speed)
// } // }
position.Update( position.Update(
position.X+(velocity.Data.X*speed.Value), position.X+(velocity.Data.X*velocity.Speed),
position.Y+(velocity.Data.Y*speed.Value), position.Y+(velocity.Data.Y*velocity.Speed),
) )
} }
@@ -89,14 +90,7 @@ func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *P
is := tile.Rect.Bounds().Intersect(object.Rect.Bounds()) is := tile.Rect.Bounds().Intersect(object.Rect.Bounds())
if is != image.ZR { if is != image.ZR {
/*
slog.Debug("Intersect",
"velocity", velocity.Data,
"player", moving.Rect,
"moved player", object.Rect,
"collision at", is,
)
*/
// collision, snap into neighbouring tile depending on the direction // collision, snap into neighbouring tile depending on the direction
switch velocity.Direction { switch velocity.Direction {
case West: case West:
@@ -109,6 +103,13 @@ func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *P
object.Update(tile.X, tile.Rect.Max.Y) object.Update(tile.X, tile.Rect.Max.Y)
} }
// slog.Debug("Intersect",
// "velocity", velocity.Data,
// "player", moving,
// "moved player", object,
// "collision at", is,
// "tilepos", tile,
// )
return true, object return true, object
} }

View File

@@ -9,6 +9,7 @@ type Velocity struct {
Data Position Data Position
Direction int Direction int
PointingAt int PointingAt int
Speed int
} }
func (velocity *Velocity) Set(new *Velocity) { func (velocity *Velocity) Set(new *Velocity) {
@@ -59,6 +60,8 @@ func (velocity *Velocity) InvertDirection() int {
case North: case North:
return South return South
case All: case All:
return All
case Stop:
return Stop return Stop
} }

77
config/config.go Normal file
View File

@@ -0,0 +1,77 @@
package config
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/knadh/koanf"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/posflag"
flag "github.com/spf13/pflag"
)
const Usage string = `openquell [options]
-t --tps <ticks> Set ticks per second, default 60.
-d --debug Enable debugging output.
`
type Config struct {
TPS int `koanf:"tps"`
Loglevel string `koanf:"loglevel"`
Debug bool `koanf:"debug"` // loglevel=debug
Startlevel int
}
func InitConfig() (*Config, error) {
var kloader = koanf.New(".")
flagset := flag.NewFlagSet("config", flag.ContinueOnError)
flagset.BoolP("debug", "d", false, "enable debug log")
flagset.Usage = func() {
fmt.Println(Usage)
os.Exit(0)
}
flagset.IntP("tps", "t", 0, "TPS")
if err := flagset.Parse(os.Args[1:]); err != nil {
return nil, fmt.Errorf("failed to parse program arguments: %w", err)
}
if err := kloader.Load(env.Provider("OC_", ".", func(s string) string {
return strings.ReplaceAll(strings.ToLower(
strings.TrimPrefix(s, "OC_")), "_", ".")
}), nil); err != nil {
return nil, fmt.Errorf("error loading environment: %w", err)
}
if err := kloader.Load(posflag.Provider(flagset, ".", kloader), nil); err != nil {
return nil, fmt.Errorf("error loading flags: %w", err)
}
conf := &Config{}
if err := kloader.Unmarshal("", &conf); err != nil {
return nil, fmt.Errorf("error unmarshalling: %w", err)
}
switch conf.Loglevel {
case "debug":
conf.Debug = true
}
// are there any args left on commandline? if so threat them as adlinks
if len(flagset.Args()) > 0 {
level, err := strconv.Atoi(flagset.Args()[0])
if err != nil {
return nil, fmt.Errorf("failed to parse start level: %w", err)
}
conf.Startlevel = level
}
return conf, nil
}

View File

@@ -1,6 +1,8 @@
package config package config
import "time" import (
"time"
)
const ( const (
Stop = iota Stop = iota
@@ -11,6 +13,13 @@ const (
All All
) )
const PLAYERSPEED int = 4 const PLAYERSPEED int = 5
const PARTICLE_LOOPWAIT time.Duration = 250 * time.Millisecond const PARTICLE_LOOPWAIT time.Duration = 250 * time.Millisecond
const LEVEL_END_WAIT time.Duration = 500 * time.Millisecond const LEVEL_END_WAIT time.Duration = 500 * time.Millisecond
const version string = "1.2.1"
const MenuRectX int = 600
const MenuRectY int = 0
const MenuRectCellsize int = 32
var VERSION string // maintained by -x

View File

@@ -4,6 +4,7 @@ import (
"image/color" "image/color"
"log/slog" "log/slog"
"openquell/assets" "openquell/assets"
"openquell/config"
"openquell/gameui" "openquell/gameui"
"github.com/ebitenui/ebitenui" "github.com/ebitenui/ebitenui"
@@ -27,7 +28,8 @@ Thomas von Dein <tom@vondein.org>.
Download it on repo.daemon.de/openquell/. Download it on repo.daemon.de/openquell/.
Copyright (c) 2024 by Thomas von Dein. Copyright (c) 2024 by Thomas von Dein.
`
Version: `
func NewAboutScene(game *Game) Scene { func NewAboutScene(game *Game) Scene {
scene := &AboutScene{Whoami: About, Game: game, Next: About} scene := &AboutScene{Whoami: About, Game: game, Next: About}
@@ -81,7 +83,7 @@ func (scene *AboutScene) SetupUI() {
) )
about := widget.NewText( about := widget.NewText(
widget.TextOpts.Text(ABOUT, *assets.FontRenderer.FontNormal, blue), widget.TextOpts.Text(ABOUT+config.VERSION, *assets.FontRenderer.FontNormal, blue),
widget.TextOpts.Position(widget.TextPositionStart, widget.TextPositionCenter), widget.TextOpts.Position(widget.TextPositionStart, widget.TextPositionCenter),
) )

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"image" "image"
"log/slog" "log/slog"
"openquell/config"
"openquell/observers" "openquell/observers"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
@@ -17,10 +18,11 @@ type Game struct {
Scenes map[SceneName]Scene Scenes map[SceneName]Scene
CurrentScene SceneName CurrentScene SceneName
Observer *observers.GameObserver Observer *observers.GameObserver
Levels []*Level // needed to feed select_scene Levels []*Level // fed in PlayScene.GenerateLevels()
Config *config.Config
} }
func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Game { func NewGame(width, height, cellsize int, cfg *config.Config, startscene SceneName) *Game {
world := ecs.NewWorld() world := ecs.NewWorld()
game := &Game{ game := &Game{
@@ -30,18 +32,17 @@ func NewGame(width, height, cellsize, startlevel int, startscene SceneName) *Gam
ScreenHeight: height, ScreenHeight: height,
Scenes: map[SceneName]Scene{}, Scenes: map[SceneName]Scene{},
Cellsize: cellsize, Cellsize: cellsize,
Config: cfg,
} }
observers.NewPlayerObserver(&world) game.Observer = observers.NewGameObserver(
observers.NewParticleObserver(&world) &world, cfg.Startlevel, width, height, cellsize)
observers.NewObstacleObserver(&world)
game.Observer = observers.NewGameObserver(&world, startlevel, width, height, cellsize)
game.Scenes[Welcome] = NewWelcomeScene(game) game.Scenes[Welcome] = NewWelcomeScene(game)
game.Scenes[Menu] = NewMenuScene(game) game.Scenes[Menu] = NewMenuScene(game)
game.Scenes[About] = NewAboutScene(game) game.Scenes[About] = NewAboutScene(game)
game.Scenes[Popup] = NewPopupScene(game) game.Scenes[Popup] = NewPopupScene(game)
game.Scenes[Play] = NewLevelScene(game, startlevel) game.Scenes[Play] = NewPlayScene(game, cfg.Startlevel)
game.Scenes[Select] = NewSelectScene(game) game.Scenes[Select] = NewSelectScene(game)
game.CurrentScene = startscene game.CurrentScene = startscene
@@ -56,22 +57,18 @@ func (game *Game) GetCurrentScene() Scene {
} }
func (game *Game) Update() error { func (game *Game) Update() error {
gameobserver := observers.GetGameObserver(game.World)
// handle level ends // handle level ends
timer := gameobserver.StopTimer timer := game.Observer.StopTimer
if timer.IsReady() { if timer.IsReady() {
// a level is either lost or won, we display a small popup // a level is either lost or won, we display a small popup
// asking the user how to continue from here // asking the user how to continue from here
timer.Reset() timer.Reset()
slog.Debug("timer ready", "lost", gameobserver.Lost, "retry", gameobserver.Retry) slog.Debug("timer ready", "lost", game.Observer.Lost, "retry", game.Observer.Retry)
if !gameobserver.Lost { game.Observer.AddScore()
gameobserver.Score++ // FIXME: use level.Score(), see TODO
}
game.Scenes[Nextlevel] = NewNextlevelScene(game, gameobserver.Lost) game.Scenes[Nextlevel] = NewNextlevelScene(game)
game.CurrentScene = Nextlevel game.CurrentScene = Nextlevel
} }
@@ -85,19 +82,19 @@ func (game *Game) Update() error {
} }
next := scene.GetNext() next := scene.GetNext()
if next != game.CurrentScene { if next != game.CurrentScene {
if next == Play && game.CurrentScene == Nextlevel { if next == Play && game.CurrentScene == Nextlevel {
// switched from nextlevel (lost or won) popup to play (either retry or next level) // switched from nextlevel (lost or won) popup to play (either retry or next level)
if !gameobserver.Retry { if !game.Observer.Retry {
gameobserver.CurrentLevel++ game.Observer.CurrentLevel++
} }
gameobserver.Retry = false game.Observer.Retry = false
} }
if next == Play { if next == Play {
// fresh setup of actual level every time we enter the play scene // fresh setup of actual level every time we enter the play scene
//game.Scenes[Play] = NewLevelScene(game, gameobserver.CurrentLevel) game.Scenes[Play].SetLevel(game.Observer.CurrentLevel)
game.Scenes[Play].SetLevel(gameobserver.CurrentLevel)
} }
// make sure we stay on the selected scene // make sure we stay on the selected scene
@@ -106,8 +103,8 @@ func (game *Game) Update() error {
// finally switch // finally switch
game.CurrentScene = next game.CurrentScene = next
// FIXME: add some reset function to gameobserver for these kinds of things // FIXME: add some reset function to game.Observer for these kinds of things
gameobserver.Lost = false game.Observer.Lost = false
} }
timer.Update() timer.Update()

View File

@@ -1,80 +0,0 @@
package game
import (
"log/slog"
"openquell/assets"
"github.com/hajimehoshi/ebiten/v2"
)
type LevelScene struct {
Game *Game
CurrentLevel int
Levels []*Level
Next SceneName
Whoami SceneName
UseCache bool
}
// Implements the actual playing Scene
func NewLevelScene(game *Game, startlevel int) Scene {
scene := &LevelScene{Whoami: Play, Next: Play, Game: game}
scene.GenerateLevels(game)
scene.SetLevel(startlevel)
return scene
}
func (scene *LevelScene) GenerateLevels(game *Game) {
for _, level := range assets.Levels {
scene.Levels = append(scene.Levels, NewLevel(game, 32, &level))
}
scene.Game.Levels = scene.Levels
}
func (scene *LevelScene) SetLevel(level int) {
scene.CurrentLevel = level
scene.Levels[scene.CurrentLevel].SetupGrid(scene.Game)
}
// Interface methods
func (scene *LevelScene) SetNext(next SceneName) {
scene.Next = next
}
func (scene *LevelScene) GetNext() SceneName {
// FIXME: set to winner or options screen
return scene.Next
}
func (scene *LevelScene) ResetNext() {
scene.Next = scene.Whoami
}
func (scene *LevelScene) Clearscreen() bool {
return false
}
func (scene *LevelScene) Update() error {
scene.Levels[scene.CurrentLevel].Update()
switch {
case ebiten.IsKeyPressed(ebiten.KeyEscape):
scene.SetNext(Popup)
}
return nil
}
func (scene *LevelScene) Draw(screen *ebiten.Image) {
if scene.CurrentLevel != scene.Game.Observer.CurrentLevel {
slog.Debug("level", "current", scene.CurrentLevel,
"next", scene.Game.Observer.CurrentLevel)
scene.CurrentLevel = scene.Game.Observer.CurrentLevel
scene.Levels[scene.CurrentLevel].SetupGrid(scene.Game)
}
screen.Clear()
scene.Levels[scene.CurrentLevel].Draw(screen)
}

View File

@@ -2,59 +2,75 @@ package game
import ( import (
"image" "image"
"log" "log/slog"
"openquell/assets" "openquell/assets"
"openquell/components" "openquell/components"
"openquell/grid" "openquell/grid"
"openquell/observers" "openquell/observers"
"openquell/systems" "openquell/systems"
"openquell/util"
"strings" "strings"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/mlange-42/arche/ecs" "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 { type Level struct {
Cellsize, Width, Height int Cellsize, Width, Height int
World *ecs.World World *ecs.World
Name string Name string
Description string Description string
Mapslice map[image.Point]*assets.Tile Number int
Mapslice Map
BackupMapslice Map
GridContainer *grid.GridContainer GridContainer *grid.GridContainer
Systems []systems.System Systems []systems.System
Grid *grid.Grid Grid *grid.Grid
} }
func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level { func NewLevel(game *Game, cellsize int, plan *ldtkgo.Level) *Level {
systemlist := []systems.System{} systemlist := []systems.System{}
gridcontainer := &grid.GridContainer{} gridcontainer := &grid.GridContainer{}
// FIXME: use plan.BGImage.Path here?
systemlist = append(systemlist, systemlist = append(systemlist,
systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize, plan.Background)) systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize,
assets.Assets[plan.PropertyByIdentifier("background").AsString()]))
systemlist = append(systemlist, systems.NewCollectibleSystem(game.World)) systemlist = append(systemlist, systems.NewCollectibleSystem(game.World))
systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer))
systemlist = append(systemlist, systemlist = append(systemlist,
systems.NewPlayerSystem(game.World, gridcontainer)) systems.NewPlayerSystem(game.World, gridcontainer, game.ScreenWidth, game.ScreenHeight))
systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize)) systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize))
systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer))
systemlist = append(systemlist, systems.NewTransientSystem(game.World, gridcontainer)) 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{ return &Level{
Mapslice: LevelToSlice(game, plan, cellsize), Mapslice: mapslice,
Cellsize: cellsize, BackupMapslice: backupmap,
World: game.World, Cellsize: cellsize,
Width: game.ScreenWidth, World: game.World,
Height: game.ScreenHeight, Width: game.ScreenWidth,
Description: plan.Description, Height: game.ScreenHeight,
Name: plan.Name, Description: plan.PropertyByIdentifier("description").AsString(),
GridContainer: gridcontainer, Number: plan.PropertyByIdentifier("level").AsInt(),
Systems: systemlist, Name: strings.ReplaceAll(plan.Identifier, "_", " "),
GridContainer: gridcontainer,
Systems: systemlist,
} }
} }
@@ -70,10 +86,9 @@ func (level *Level) Draw(screen *ebiten.Image) {
} }
} }
func (level *Level) Position2Point(position *components.Position) image.Point { func (level *Level) RestoreMap() {
return image.Point{ for point, tile := range level.BackupMapslice {
int(position.X) / level.Cellsize, level.Mapslice[point] = tile.Clone()
int(position.Y) / level.Cellsize,
} }
} }
@@ -89,33 +104,91 @@ func (level *Level) SetupGrid(game *Game) {
level.World.Batch().RemoveEntities(selector) level.World.Batch().RemoveEntities(selector)
// get rid of any players on PlayerObserver. FIXME: remove them in grid.NewGrid()? // get rid of any players on PlayerObserver. FIXME: remove them in grid.NewGrid()?
playerobserver := observers.GetPlayerObserver(level.World) observer := observers.GetGameObserver(level.World)
playerobserver.RemoveEntities() observer.RemoveEntities()
// get rid of possibly manipulated map
level.RestoreMap()
// setup world // setup world
slog.Debug("new grid?")
level.GridContainer.SetGrid( level.GridContainer.SetGrid(
grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice)) 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 NewMapSlice(game *Game, tilesize int) Map {
func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) map[image.Point]*assets.Tile {
size := game.ScreenWidth * game.ScreenHeight size := game.ScreenWidth * game.ScreenHeight
mapslice := make(map[image.Point]*assets.Tile, size) mapslice := make(Map, size)
for y, line := range strings.Split(string(level.Data), "\n") { for y := 0; y < game.ScreenHeight; y += 32 {
if len(line) != game.ScreenWidth/tilesize && y < game.ScreenHeight/tilesize { for x := 0; x < game.ScreenWidth; x += 32 {
log.Fatalf("line %d doesn't contain %d tiles, but %d", mapslice[image.Point{x / tilesize, y / tilesize}] = assets.Tiles["floor"]
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)
}
mapslice[image.Point{x, y}] = assets.Tiles[byte(char)]
} }
} }
return mapslice 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["primarymap"]
for _, entity := range layer.Entities {
if entity.TileRect != nil {
tile := assets.Tiles[entity.Identifier]
slog.Debug("LOAD TILE", "tile", 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
}

View File

@@ -1,6 +1,7 @@
package game package game
import ( import (
"fmt"
"image/color" "image/color"
"log/slog" "log/slog"
"openquell/assets" "openquell/assets"
@@ -22,12 +23,11 @@ type NextlevelScene struct {
Lost bool Lost bool
} }
func NewNextlevelScene(game *Game, lost bool) Scene { func NewNextlevelScene(game *Game) Scene {
scene := &NextlevelScene{ scene := &NextlevelScene{
Whoami: Nextlevel, Whoami: Nextlevel,
Game: game, Game: game,
Next: Nextlevel, Next: Nextlevel,
Lost: lost,
} }
scene.SetupUI() scene.SetupUI()
@@ -61,7 +61,7 @@ func (scene *NextlevelScene) Update() error {
func (scene *NextlevelScene) SetLevel(level int) {} func (scene *NextlevelScene) SetLevel(level int) {}
func (scene *NextlevelScene) Draw(screen *ebiten.Image) { func (scene *NextlevelScene) Draw(screen *ebiten.Image) {
background := assets.Assets["background-popup"] background := assets.Assets["background-popup-wide"]
op := &ebiten.DrawImageOptions{} op := &ebiten.DrawImageOptions{}
op.GeoM.Translate( op.GeoM.Translate(
@@ -69,34 +69,81 @@ func (scene *NextlevelScene) Draw(screen *ebiten.Image) {
float64((scene.Game.ScreenHeight/2)-(background.Bounds().Dy()/2)), float64((scene.Game.ScreenHeight/2)-(background.Bounds().Dy()/2)),
) )
screen.DrawImage(assets.Assets["background-popup"], op) screen.DrawImage(background, op)
scene.Ui.Draw(screen) scene.Ui.Draw(screen)
} }
func (scene *NextlevelScene) SetupUI() { func (scene *NextlevelScene) SetupUI() {
observer := observers.GetGameObserver(scene.Game.World)
slog.Debug("levels", "max", observer.MaxLevels, "current", observer.CurrentLevel)
if observer.MaxLevels == observer.CurrentLevel+1 && !observer.Lost {
scene.SetupUILast()
} else {
scene.SetupUIRetry()
}
}
func (scene *NextlevelScene) SetupUILast() {
blue := color.RGBA{0, 255, 128, 255} blue := color.RGBA{0, 255, 128, 255}
gameobserver := observers.GetGameObserver(scene.Game.World) observer := observers.GetGameObserver(scene.Game.World)
rowContainer := gameui.NewRowContainer(false) rowContainer := gameui.NewRowContainer(false)
labeltext := "Success" label1text := "Last level, congratulations!"
label2text := fmt.Sprintf("Your final score: %d", observer.GetScore())
switch scene.Lost { buttonMenu := gameui.NewMenuButton("Menu", *assets.FontRenderer.FontNormal,
func(args *widget.ButtonClickedEventArgs) {
scene.SetNext(Menu)
})
label1 := widget.NewText(
widget.TextOpts.Text(label1text, *assets.FontRenderer.FontNormal, blue),
widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter),
)
label2 := widget.NewText(
widget.TextOpts.Text(label2text, *assets.FontRenderer.FontNormal, blue),
widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter),
)
rowContainer.AddChild(label1)
rowContainer.AddChild(label2)
rowContainer.AddChild(buttonMenu)
scene.Ui = &ebitenui.UI{
Container: rowContainer.Container(),
}
}
func (scene *NextlevelScene) SetupUIRetry() {
blue := color.RGBA{0, 255, 128, 255}
observer := observers.GetGameObserver(scene.Game.World)
buttonNext := &widget.Button{}
rowContainer := gameui.NewRowContainer(false)
labeltext := fmt.Sprintf("Great! (Score: %d)", observer.GetLevelScore())
islast := observer.MaxLevels == observer.CurrentLevel+1
switch observer.Lost {
case true: case true:
labeltext = "Failure" labeltext = "Too bad!"
} }
buttonRetry := gameui.NewMenuButton("Retry", *assets.FontRenderer.FontNormal, buttonRetry := gameui.NewMenuButton("Retry", *assets.FontRenderer.FontNormal,
func(args *widget.ButtonClickedEventArgs) { func(args *widget.ButtonClickedEventArgs) {
scene.SetNext(Play) scene.SetNext(Play)
gameobserver.Retry = true observer.Retry = true
}) })
buttonNext := gameui.NewMenuButton("Next Level", *assets.FontRenderer.FontNormal, if !islast {
func(args *widget.ButtonClickedEventArgs) { buttonNext = gameui.NewMenuButton("Next Level", *assets.FontRenderer.FontNormal,
scene.SetNext(Play) func(args *widget.ButtonClickedEventArgs) {
gameobserver.Retry = false scene.SetNext(Play)
}) observer.Retry = false
})
}
buttonAbort := gameui.NewMenuButton("Abort", *assets.FontRenderer.FontNormal, buttonAbort := gameui.NewMenuButton("Abort", *assets.FontRenderer.FontNormal,
func(args *widget.ButtonClickedEventArgs) { func(args *widget.ButtonClickedEventArgs) {
@@ -104,12 +151,14 @@ func (scene *NextlevelScene) SetupUI() {
}) })
label := widget.NewText( label := widget.NewText(
widget.TextOpts.Text(labeltext, *assets.FontRenderer.FontBig, blue), widget.TextOpts.Text(labeltext, *assets.FontRenderer.FontNormal, blue),
widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter), widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter),
) )
rowContainer.AddChild(label) rowContainer.AddChild(label)
rowContainer.AddChild(buttonNext) if !islast {
rowContainer.AddChild(buttonNext)
}
rowContainer.AddChild(buttonRetry) rowContainer.AddChild(buttonRetry)
rowContainer.AddChild(buttonAbort) rowContainer.AddChild(buttonAbort)

101
game/play_scene.go Normal file
View File

@@ -0,0 +1,101 @@
package game
import (
"image"
"log/slog"
"openquell/assets"
"openquell/config"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
type PlayScene struct {
Game *Game
CurrentLevel int
Levels []*Level
Next SceneName
Whoami SceneName
UseCache bool
MenuRect image.Rectangle
}
// Implements the actual playing Scene
func NewPlayScene(game *Game, startlevel int) Scene {
scene := &PlayScene{Whoami: Play, Next: Play, Game: game}
scene.GenerateLevels(game)
scene.SetLevel(startlevel)
scene.MenuRect = image.Rect(config.MenuRectX, config.MenuRectY,
config.MenuRectX+config.MenuRectCellsize,
config.MenuRectY+config.MenuRectCellsize)
return scene
}
func (scene *PlayScene) GenerateLevels(game *Game) {
min := []int{}
for _, level := range assets.Project.Levels {
level := level
scene.Levels = append(scene.Levels, NewLevel(game, 32, level))
min = append(min, level.PropertyByIdentifier("minmoves").AsInt())
}
scene.Game.Observer.SetupLevelScore(min)
scene.Game.Observer.SetupMaxLevels(len(min))
scene.Game.Levels = scene.Levels
}
func (scene *PlayScene) SetLevel(level int) {
scene.CurrentLevel = level
scene.Levels[scene.CurrentLevel].SetupGrid(scene.Game)
}
// Interface methods
func (scene *PlayScene) SetNext(next SceneName) {
scene.Next = next
}
func (scene *PlayScene) GetNext() SceneName {
// FIXME: set to winner or options screen
return scene.Next
}
func (scene *PlayScene) ResetNext() {
scene.Next = scene.Whoami
}
func (scene *PlayScene) Clearscreen() bool {
return false
}
func (scene *PlayScene) Update() error {
scene.Levels[scene.CurrentLevel].Update()
switch {
case inpututil.IsKeyJustPressed(ebiten.KeyEscape):
scene.SetNext(Popup)
case inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft):
// ok we're checking the menu button here, but drawing it in hud_system,
// because systems can't switch scenes
if image.Pt(ebiten.CursorPosition()).In(scene.MenuRect) {
scene.SetNext(Popup)
}
}
return nil
}
func (scene *PlayScene) Draw(screen *ebiten.Image) {
// FIXME: why not in Update() ?!?!?!
if scene.CurrentLevel != scene.Game.Observer.CurrentLevel {
slog.Debug("level", "current", scene.CurrentLevel,
"next", scene.Game.Observer.CurrentLevel)
scene.CurrentLevel = scene.Game.Observer.CurrentLevel
scene.Levels[scene.CurrentLevel].SetupGrid(scene.Game)
}
screen.Clear()
scene.Levels[scene.CurrentLevel].Draw(screen)
}

View File

@@ -5,11 +5,13 @@ import (
"log/slog" "log/slog"
"openquell/assets" "openquell/assets"
"openquell/gameui" "openquell/gameui"
"openquell/observers"
"github.com/ebitenui/ebitenui" "github.com/ebitenui/ebitenui"
"github.com/ebitenui/ebitenui/widget" "github.com/ebitenui/ebitenui/widget"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
) )
type PopupScene struct { type PopupScene struct {
@@ -50,6 +52,11 @@ func (scene *PopupScene) Clearscreen() bool {
func (scene *PopupScene) Update() error { func (scene *PopupScene) Update() error {
scene.Ui.Update() scene.Ui.Update()
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
scene.SetNext(Play)
}
return nil return nil
} }
@@ -69,6 +76,7 @@ func (scene *PopupScene) Draw(screen *ebiten.Image) {
func (scene *PopupScene) SetupUI() { func (scene *PopupScene) SetupUI() {
blue := color.RGBA{0, 255, 128, 255} blue := color.RGBA{0, 255, 128, 255}
observer := observers.GetGameObserver(scene.Game.World)
rowContainer := gameui.NewRowContainer(false) rowContainer := gameui.NewRowContainer(false)
@@ -82,9 +90,10 @@ func (scene *PopupScene) SetupUI() {
scene.SetNext(Menu) scene.SetNext(Menu)
}) })
buttonOptions := gameui.NewMenuButton("Options", *assets.FontRenderer.FontNormal, buttonRetry := gameui.NewMenuButton("Retry", *assets.FontRenderer.FontNormal,
func(args *widget.ButtonClickedEventArgs) { func(args *widget.ButtonClickedEventArgs) {
scene.SetNext(Settings) scene.SetNext(Play)
observer.Retry = true
}) })
label := widget.NewText( label := widget.NewText(
@@ -94,8 +103,8 @@ func (scene *PopupScene) SetupUI() {
rowContainer.AddChild(label) rowContainer.AddChild(label)
rowContainer.AddChild(buttonContinue) rowContainer.AddChild(buttonContinue)
rowContainer.AddChild(buttonRetry)
rowContainer.AddChild(buttonAbort) rowContainer.AddChild(buttonAbort)
rowContainer.AddChild(buttonOptions)
scene.Ui = &ebitenui.UI{ scene.Ui = &ebitenui.UI{
Container: rowContainer.Container(), Container: rowContainer.Container(),

View File

@@ -13,6 +13,7 @@ import (
"github.com/ebitenui/ebitenui/widget" "github.com/ebitenui/ebitenui/widget"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
) )
type SelectScene struct { type SelectScene struct {
@@ -53,6 +54,11 @@ func (scene *SelectScene) Clearscreen() bool {
func (scene *SelectScene) Update() error { func (scene *SelectScene) Update() error {
scene.Ui.Update() scene.Ui.Update()
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
scene.SetNext(Menu)
}
return nil return nil
} }
@@ -61,8 +67,9 @@ func (scene *SelectScene) Draw(screen *ebiten.Image) {
} }
type LevelEntry struct { type LevelEntry struct {
Id int Id int
Name string Number int
Name string
} }
func (scene *SelectScene) SetupUI() { func (scene *SelectScene) SetupUI() {
@@ -79,7 +86,7 @@ func (scene *SelectScene) SetupUI() {
levels := make([]any, 0, len(scene.Game.Levels)) levels := make([]any, 0, len(scene.Game.Levels))
for id := 0; id < len(scene.Game.Levels); id++ { for id := 0; id < len(scene.Game.Levels); id++ {
levels = append(levels, LevelEntry{Id: id, Name: scene.Game.Levels[id].Name}) levels = append(levels, LevelEntry{Id: id, Number: scene.Game.Levels[id].Number, Name: scene.Game.Levels[id].Name})
} }
slog.Debug("levels", "levels", levels) slog.Debug("levels", "levels", levels)
@@ -92,10 +99,10 @@ func (scene *SelectScene) SetupUI() {
// Set how wide the list should be // Set how wide the list should be
widget.ListOpts.ContainerOpts(widget.ContainerOpts.WidgetOpts( widget.ListOpts.ContainerOpts(widget.ContainerOpts.WidgetOpts(
widget.WidgetOpts.MinSize(150, 0), widget.WidgetOpts.MinSize(150, 0),
widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{ widget.WidgetOpts.LayoutData(widget.RowLayoutData{
HorizontalPosition: widget.AnchorLayoutPositionCenter, Position: widget.RowLayoutPositionCenter,
VerticalPosition: widget.AnchorLayoutPositionEnd, Stretch: true,
StretchVertical: true, MaxHeight: 200,
}), }),
)), )),
// Set the entries in the list // Set the entries in the list
@@ -123,18 +130,18 @@ func (scene *SelectScene) SetupUI() {
widget.ListOpts.EntryFontFace(*assets.FontRenderer.FontNormal), widget.ListOpts.EntryFontFace(*assets.FontRenderer.FontNormal),
// Set the colors for the list // Set the colors for the list
widget.ListOpts.EntryColor(&widget.ListEntryColor{ widget.ListOpts.EntryColor(&widget.ListEntryColor{
Selected: color.NRGBA{0, 255, 0, 255}, // Foreground color for the unfocused selected entry Selected: color.NRGBA{0, 255, 0, 255},
Unselected: color.NRGBA{254, 255, 255, 255}, // Foreground color for the unfocused unselected entry Unselected: color.NRGBA{254, 255, 255, 255},
SelectedBackground: color.NRGBA{R: 130, G: 130, B: 200, A: 255}, // Background color for the unfocused selected entry SelectedBackground: color.NRGBA{R: 130, G: 130, B: 200, A: 255},
SelectedFocusedBackground: color.NRGBA{R: 130, G: 130, B: 170, A: 255}, // Background color for the focused selected entry SelectedFocusedBackground: color.NRGBA{R: 130, G: 130, B: 170, A: 255},
FocusedBackground: color.NRGBA{R: 170, G: 170, B: 180, A: 255}, // Background color for the focused unselected entry FocusedBackground: color.NRGBA{R: 170, G: 170, B: 180, A: 255},
DisabledUnselected: color.NRGBA{100, 100, 100, 255}, // Foreground color for the disabled unselected entry DisabledUnselected: color.NRGBA{100, 100, 100, 255},
DisabledSelected: color.NRGBA{100, 100, 100, 255}, // Foreground color for the disabled selected entry DisabledSelected: color.NRGBA{100, 100, 100, 255},
DisabledSelectedBackground: color.NRGBA{100, 100, 100, 255}, // Background color for the disabled selected entry DisabledSelectedBackground: color.NRGBA{100, 100, 100, 255},
}), }),
// This required function returns the string displayed in the list // This required function returns the string displayed in the list
widget.ListOpts.EntryLabelFunc(func(e interface{}) string { widget.ListOpts.EntryLabelFunc(func(e interface{}) string {
return e.(LevelEntry).Name return fmt.Sprintf("%02d %s", e.(LevelEntry).Number, e.(LevelEntry).Name)
}), }),
// Padding for each entry // Padding for each entry
widget.ListOpts.EntryTextPadding(widget.NewInsetsSimple(5)), widget.ListOpts.EntryTextPadding(widget.NewInsetsSimple(5)),
@@ -148,14 +155,31 @@ func (scene *SelectScene) SetupUI() {
}), }),
) )
buttonContainer := widget.NewContainer(
widget.ContainerOpts.Layout(widget.NewGridLayout(
widget.GridLayoutOpts.Columns(2),
widget.GridLayoutOpts.Padding(widget.NewInsetsSimple(30)),
widget.GridLayoutOpts.Spacing(20, 10),
widget.GridLayoutOpts.Stretch([]bool{true, false}, []bool{false, true}),
)),
)
buttonPlay := gameui.NewMenuButton("Play selected level", *assets.FontRenderer.FontNormal, buttonPlay := gameui.NewMenuButton("Play selected level", *assets.FontRenderer.FontNormal,
func(args *widget.ButtonClickedEventArgs) { func(args *widget.ButtonClickedEventArgs) {
scene.SetNext(Play) scene.SetNext(Play)
}) })
buttonAbort := gameui.NewMenuButton("Back", *assets.FontRenderer.FontNormal,
func(args *widget.ButtonClickedEventArgs) {
scene.SetNext(Menu)
})
buttonContainer.AddChild(buttonPlay)
buttonContainer.AddChild(buttonAbort)
rowContainer.AddChild(label) rowContainer.AddChild(label)
rowContainer.AddChild(list) rowContainer.AddChild(list)
rowContainer.AddChild(buttonPlay) rowContainer.AddChild(buttonContainer)
scene.Ui = &ebitenui.UI{ scene.Ui = &ebitenui.UI{
Container: rowContainer.Container(), Container: rowContainer.Container(),

40
go.mod
View File

@@ -3,29 +3,37 @@ module openquell
go 1.21 go 1.21
require ( require (
github.com/alecthomas/repr v0.3.0
github.com/ebitenui/ebitenui v0.5.6-0.20240319232637-752494bdfcac
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/hajimehoshi/ebiten/v2 v2.6.6 github.com/hajimehoshi/ebiten/v2 v2.6.6
github.com/knadh/koanf v1.5.0
github.com/mlange-42/arche v0.10.0 github.com/mlange-42/arche v0.10.0
github.com/solarlune/ldtkgo v0.9.4-0.20240310011150-66aa15c2ab56
github.com/spf13/pflag v1.0.5
github.com/tinne26/etxt v0.0.8
github.com/tlinden/yadu v0.1.3
golang.org/x/image v0.15.0
) )
require ( require (
github.com/alecthomas/repr v0.3.0 // indirect github.com/ebitengine/purego v0.6.1 // indirect
github.com/ebitengine/purego v0.6.0 // indirect
github.com/ebitenui/ebitenui v0.5.5 // indirect
github.com/fatih/color v1.16.0 // indirect github.com/fatih/color v1.16.0 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect github.com/jezek/xgb v1.1.1 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/hajimehoshi/ebiten v1.12.12 // indirect
github.com/jezek/xgb v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/tinne26/etxt v0.0.8 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/tlinden/yadu v0.1.3 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect
golang.org/x/image v0.12.0 // indirect github.com/tidwall/gjson v1.9.3 // indirect
golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 // indirect github.com/tidwall/match v1.1.1 // indirect
golang.org/x/sync v0.3.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/sys v0.14.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/exp/shiny v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mobile v0.0.0-20240213143359-d1f7d3436075 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

478
go.sum
View File

@@ -1,133 +1,445 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8= github.com/alecthomas/repr v0.3.0 h1:NeYzUPfjjlqHY4KtzgKJiWd6sVq2eNUPTi34PiFGjY8=
github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.3.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/ebitengine/purego v0.5.0 h1:JrMGKfRIAM4/QVKaesIIT7m/UVjTj5GYhRSQYwfVdpo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/ebitengine/purego v0.5.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/ebitengine/purego v0.6.0 h1:Yo9uBc1x+ETQbfEaf6wcBsjrQfCEnh/gaGUg7lguEJY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/ebitengine/purego v0.6.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/ebitenui/ebitenui v0.5.5 h1:L9UCWmiMlo4sG5TavQKmjfsnwMmYqkld2tXWZMmKkSA= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/ebitenui/ebitenui v0.5.5/go.mod h1:CkzAwu9Ks32P+NC/7+iypdLA85Wqnn93UztPFE+kAH4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/ebitengine/purego v0.6.1 h1:sjN8rfzbhXQ59/pE+wInswbU9aMDHiwlup4p/a07Mkg=
github.com/ebitengine/purego v0.6.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/ebitenui/ebitenui v0.5.6-0.20240319232637-752494bdfcac h1:lwCaGTkCrK7omCX9HEiWjxClpUg+NIvTpyUDk9C0pdo=
github.com/ebitenui/ebitenui v0.5.6-0.20240319232637-752494bdfcac/go.mod h1:I0rVbTOUi7gWKTPet2gzbvhOdkHp5pJXMM6c6b3dRoE=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/hajimehoshi/bitmapfont v1.3.0/go.mod h1:/Qb7yVjHYNUV4JdqNkPs6BSZwLjKqkZOMIp6jZD0KgE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/hajimehoshi/ebiten v1.12.12 h1:JvmF1bXRa+t+/CcLWxrJCRsdjs2GyBYBSiFAfIqDFlI= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/hajimehoshi/ebiten v1.12.12/go.mod h1:1XI25ImVCDPJiXox4h9yK/CvN5sjDYnbF4oZcFzPXHw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hajimehoshi/ebiten/v2 v2.6.5 h1:lALv+qhEK3CBWViyiGpz4YcR6slVJEjCiS7sExKZ9OE= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hajimehoshi/ebiten/v2 v2.6.5/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4=
github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA=
github.com/hajimehoshi/ebiten/v2 v2.6.6 h1:E5X87Or4VwKZIKjeC9+Vr4ComhZAz9h839myF4Q21kc= github.com/hajimehoshi/ebiten/v2 v2.6.6 h1:E5X87Or4VwKZIKjeC9+Vr4ComhZAz9h839myF4Q21kc=
github.com/hajimehoshi/ebiten/v2 v2.6.6/go.mod h1:gKgQI26zfoSb6j5QbrEz2L6nuHMbAYwrsXa5qsGrQKo= github.com/hajimehoshi/ebiten/v2 v2.6.6/go.mod h1:gKgQI26zfoSb6j5QbrEz2L6nuHMbAYwrsXa5qsGrQKo=
github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mlange-42/arche v0.10.0 h1:fEFDAYMAnWa+xHc1oq4gVcA4PuEQOCGSRXSKITXawMw= github.com/mlange-42/arche v0.10.0 h1:fEFDAYMAnWa+xHc1oq4gVcA4PuEQOCGSRXSKITXawMw=
github.com/mlange-42/arche v0.10.0/go.mod h1:gJ5J8vBreqrf4TcBomBFPGnWdE5P3qa4LtxYHn1gDcg= github.com/mlange-42/arche v0.10.0/go.mod h1:gJ5J8vBreqrf4TcBomBFPGnWdE5P3qa4LtxYHn1gDcg=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/solarlune/ldtkgo v0.9.4-0.20240310011150-66aa15c2ab56 h1:QW8w9YQbIlIW053jM2SfBKAFGyd4maoq0AawZsb9rO4=
github.com/solarlune/ldtkgo v0.9.4-0.20240310011150-66aa15c2ab56/go.mod h1:PP4XtlnCSwWo7iexI/NJ3aS3INmiT42o066o6Dx52rs=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E=
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinne26/etxt v0.0.8 h1:rjb58jkMkapRGLmhBMWnT76E/nMTXC5P1Q956BRZkoc= github.com/tinne26/etxt v0.0.8 h1:rjb58jkMkapRGLmhBMWnT76E/nMTXC5P1Q956BRZkoc=
github.com/tinne26/etxt v0.0.8/go.mod h1:QM/hlNkstsKC39elTFNKAR34xsMb9QoVosf+g9wlYxM= github.com/tinne26/etxt v0.0.8/go.mod h1:QM/hlNkstsKC39elTFNKAR34xsMb9QoVosf+g9wlYxM=
github.com/tlinden/yadu v0.1.2 h1:TYYVnUJwziRJ9YPbIbRf9ikmDw0Q8Ifixm+J/kBQFh8=
github.com/tlinden/yadu v0.1.2/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA=
github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY= github.com/tlinden/yadu v0.1.3 h1:5cRCUmj+l5yvlM2irtpFBIJwVV2DPEgYSaWvF19FtcY=
github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA= github.com/tlinden/yadu v0.1.3/go.mod h1:l3bRmHKL9zGAR6pnBHY2HRPxBecf7L74BoBgOOpTcUA=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= golang.org/x/exp/shiny v0.0.0-20240222234643-814bf88cf225 h1:5c1vh6Z0LHEVurVuFE5ElIYhjVG+nP7ZGFB3yx9yTVA=
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/exp/shiny v0.0.0-20240222234643-814bf88cf225/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ= golang.org/x/mobile v0.0.0-20240213143359-d1f7d3436075 h1:iZzqyDd8gFkJZpsJNzveyScRBcQlsveheh6Q77LzhPY=
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk= golang.org/x/mobile v0.0.0-20240213143359-d1f7d3436075/go.mod h1:Y8Bnziw2dX69ZhYuqQB8Ihyjks1Q6fMmbg17j9+ISNA=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20210208171126-f462b3930c8f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 h1:Q6NT8ckDYNcwmi/bmxe+XbiDMXqMRW1xFBtJ+bIpie4=
golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57/go.mod h1:wEyOn6VvNW7tcf+bW/wBz1sehi2s2BZ4TimyR7qZen4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -5,6 +5,36 @@ import (
. "openquell/config" . "openquell/config"
) )
// Check a collision on the grid. We check if the entity in question
// bumps into the egde of the grid or if it bumps onto a solid wall
// tile. For both cases the user must provide responder funcs in which
// it must be implemented how to react on those events.
func (grid *Grid) CheckGridCollision(
position *components.Position,
velocity *components.Velocity,
respond_edge func(*components.Position, *components.Velocity, *components.Position),
respond_solid func(*components.Position, *components.Velocity, *components.Position),
) {
if velocity.Moving() {
ok, newpos := grid.BumpEdge(position, velocity)
if ok {
//slog.Debug("falling off the edge", "newpos", newpos)
respond_edge(position, velocity, newpos)
} else {
ok, tilepos := grid.GetSolidNeighborPosition(position, velocity, true)
if ok {
// slog.Debug("HaveSolidNeighbor", "ok", ok, "tilepos",
// tilepos.Point(), "playerpos", playerposition)
intersects, newpos := tilepos.Intersects(position, velocity)
if intersects {
respond_solid(position, velocity, newpos)
}
}
}
}
}
func (grid *Grid) GetSolidNeighborPosition( func (grid *Grid) GetSolidNeighborPosition(
position *components.Position, position *components.Position,
velocity *components.Velocity, velocity *components.Velocity,
@@ -14,7 +44,7 @@ func (grid *Grid) GetSolidNeighborPosition(
return false, nil return false, nil
} }
// set to true, if we are already on the last tile in the current // set to true if we are already on the last tile in the current
// direction, i.e. on the edge of the grid // direction, i.e. on the edge of the grid
edge := true edge := true
neighborpos := position.Point() neighborpos := position.Point()
@@ -44,6 +74,9 @@ func (grid *Grid) GetSolidNeighborPosition(
newpos := components.NewPosition(neighborpos, grid.Tilesize) newpos := components.NewPosition(neighborpos, grid.Tilesize)
// slog.Debug("SolidNeighbor?", "player", position.Point(),
// "neighbor", neighborpos, "edge", edge, "neighbor-solid",
// grid.Map[neighborpos].Solid, "newpos", newpos.Point())
if !edge && grid.Map[neighborpos].Solid { if !edge && grid.Map[neighborpos].Solid {
return true, newpos return true, newpos
} }

View File

@@ -29,11 +29,10 @@ func NewGrid(world *ecs.World,
// we use this to turn our tiles into iterable entities, used for // we use this to turn our tiles into iterable entities, used for
// collision detection, transformation and other things // collision detection, transformation and other things
playermapper := generic.NewMap5[ playermapper := generic.NewMap4[
components.Position, components.Position,
components.Velocity, components.Velocity,
components.Renderable, components.Renderable,
components.Speed,
components.Player](world) components.Player](world)
solidmapper := generic.NewMap4[ solidmapper := generic.NewMap4[
@@ -49,11 +48,10 @@ func NewGrid(world *ecs.World,
components.Renderable, components.Renderable,
components.Collectible](world) components.Collectible](world)
obsmapper := generic.NewMap5[ obsmapper := generic.NewMap4[
components.Position, components.Position,
components.Velocity, components.Velocity,
components.Renderable, components.Renderable,
components.Speed,
components.Obstacle](world) components.Obstacle](world)
transmapper := generic.NewMap3[ transmapper := generic.NewMap3[
@@ -61,15 +59,22 @@ func NewGrid(world *ecs.World,
components.Renderable, components.Renderable,
components.Transient](world) components.Transient](world)
doormapper := generic.NewMap3[
components.Position,
components.Renderable,
components.Destroyable](world)
var pos *components.Position var pos *components.Position
var vel *components.Velocity var vel *components.Velocity
var render *components.Renderable var render *components.Renderable
var speed *components.Speed
var transient *components.Transient var transient *components.Transient
var player *components.Player var player *components.Player
var destroyable *components.Destroyable
playerobserver := observers.GetPlayerObserver(world) playerID := ecs.ComponentID[components.Player](world)
obstacleobserver := observers.GetObstacleObserver(world) obstacleID := ecs.ComponentID[components.Obstacle](world)
observer := observers.GetGameObserver(world)
for point, tile := range mapslice { for point, tile := range mapslice {
switch tile.Renderable { switch tile.Renderable {
@@ -80,29 +85,41 @@ func NewGrid(world *ecs.World,
pos, render, _, _ = solidmapper.Get(entity) pos, render, _, _ = solidmapper.Get(entity)
case tile.Player: case tile.Player:
entity := playermapper.New() entity := playermapper.New()
pos, _, render, speed, player = playermapper.Get(entity) pos, vel, render, player = playermapper.Get(entity)
playerobserver.AddEntity(entity) observer.AddEntity(entity, playerID)
speed.Value = config.PLAYERSPEED vel.Speed = config.PLAYERSPEED
player.IsPrimary = tile.IsPrimary player.IsPrimary = tile.IsPrimary
player.Sprites = tile.Tiles player.Sprites = tile.Tiles
player.LoopPos = &components.Position{Cellsize: tilesize}
case tile.Collectible: case tile.Collectible:
entity := colmapper.New() entity := colmapper.New()
pos, render, _ = colmapper.Get(entity) pos, render, _ = colmapper.Get(entity)
case tile.Obstacle: case tile.Obstacle:
entity := obsmapper.New() entity := obsmapper.New()
pos, vel, render, speed, _ = obsmapper.Get(entity) pos, vel, render, _ = obsmapper.Get(entity)
vel.Direction = tile.Direction vel.Direction = tile.Direction
vel.PointingAt = tile.Direction vel.PointingAt = tile.Direction
speed.Value = config.PLAYERSPEED vel.Speed = config.PLAYERSPEED
obstacleobserver.AddEntity(entity) observer.AddEntity(entity, obstacleID)
case tile.Transient: case tile.Transient:
entity := transmapper.New() entity := transmapper.New()
pos, render, transient = transmapper.Get(entity) pos, render, transient = transmapper.Get(entity)
transient.Sprites = tile.TileNames transient.Sprites = tile.TileNames
case tile.Destroyable:
entity := doormapper.New()
pos, render, destroyable = doormapper.Get(entity)
destroyable.Sprites = tile.Tiles
render.DamageImage = tile.Alpha
render.Shader = tile.Shader
render.Damaged = 0
default: default:
log.Fatalln("unsupported tile type encountered") log.Fatalln("unsupported tile type encountered")
} }
// FIXME: this image is never being used because it is
// being overwritten in game/levels.go:LevelToSlice(). The
// image is taken from the LDTK map, not from the Tile{}
// definition anymore
render.Image = tile.Sprite render.Image = tile.Sprite
default: default:
@@ -143,7 +160,15 @@ func (grid *Grid) GetTile(
return tile return tile
} }
func (grid *Grid) SetTile(tile *assets.Tile, point image.Point) { 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) {
solidmapper := generic.NewMap4[ solidmapper := generic.NewMap4[
components.Position, components.Position,
components.Renderable, components.Renderable,

16
index.html Normal file
View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<script src="wasm_exec.js"></script>
<script>
// Polyfill
if (!WebAssembly.instantiateStreaming) {
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
const go = new Go();
WebAssembly.instantiateStreaming(fetch("openquell.wasm"), go.importObject).then(result => {
go.run(result.instance);
});
</script>

View File

@@ -1,18 +0,0 @@
(define-derived-mode level-mode text-mode "level mode"
"game levels"
(setq buffer-face-mode-face '(:family "Monaco-10:spacing=110" :height 100 ))
)
(defun level-delete-char()
(interactive)
(when (< 2 (line-number-at-pos))
(delete-char 1)))
(add-hook 'level-mode-hook 'whitespace-mode)
(add-hook 'level-mode-hook (lambda() (hungry-delete-mode 0)))
(add-hook 'level-mode-hook
(lambda ()
(add-hook 'post-self-insert-hook 'level-delete-char nil 'make-it-local)))
(provide 'level-mode)

2
local.html Normal file
View File

@@ -0,0 +1,2 @@
<!DOCTYPE html>
<iframe src="main.html" width="640" height="480"></iframe>

22
main.go
View File

@@ -1,11 +1,14 @@
package main package main
import ( import (
"fmt"
"log" "log"
"log/slog" "log/slog"
"openquell/config"
"openquell/game" "openquell/game"
"os" "os"
"github.com/alecthomas/repr"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
"github.com/tlinden/yadu" "github.com/tlinden/yadu"
) )
@@ -20,22 +23,35 @@ func main() {
ebiten.SetWindowSize(width, height) ebiten.SetWindowSize(width, height)
ebiten.SetWindowTitle("openquell") ebiten.SetWindowTitle("openquell")
config, err := config.InitConfig()
if err != nil {
log.Fatal(err)
}
repr.Print(config)
if config.TPS > 0 {
fmt.Printf("Setting TPS to %d\n", config.TPS)
ebiten.SetMaxTPS(config.TPS)
}
logLevel := &slog.LevelVar{} logLevel := &slog.LevelVar{}
opts := &yadu.Options{ opts := &yadu.Options{
Level: logLevel, Level: logLevel,
AddSource: true, AddSource: true,
} }
logLevel.Set(slog.LevelDebug) if config.Debug {
logLevel.Set(slog.LevelDebug)
}
handler := yadu.NewHandler(os.Stdout, opts) handler := yadu.NewHandler(os.Stdout, opts)
debuglogger := slog.New(handler) debuglogger := slog.New(handler)
slog.SetDefault(debuglogger) slog.SetDefault(debuglogger)
g := game.NewGame(width, height, cellsize, 0, game.Welcome) g := game.NewGame(width, height, cellsize, config, game.Welcome)
err := ebiten.RunGame(g) err = ebiten.RunGame(g)
if err != nil { if err != nil {
log.Fatalf("unable to run game: %s", err) log.Fatalf("unable to run game: %s", err)
} }

View File

@@ -1,31 +1,81 @@
package observers package observers
import ( import (
"math/rand"
"openquell/components" "openquell/components"
"log/slog"
"github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/ecs/event"
"github.com/mlange-42/arche/generic" "github.com/mlange-42/arche/generic"
"github.com/mlange-42/arche/listener"
) )
// Used for global game state type Score struct {
type GameObserver struct { Min, Score int
CurrentLevel, Width int
Height, Cellsize, Score int
StopTimer *components.Timer
Lost bool // set to true if player is struck or something, by default: win!
Retry bool
NextlevelText string
} }
func NewGameObserver(world *ecs.World, startlevel, width, height, cellsize int) *GameObserver { // Used for global game state. Also stores mobile entities of the
// current level. The Entities map will be reset on each level
// initialization in grid/grid.go
type GameObserver struct {
World *ecs.World
CurrentLevel, Width, Moves int
Height, Cellsize, Score int
StopTimer *components.Timer
Lost bool // set to true if player is struck or something, by default: win!
Retry bool
NextlevelText string
Entities map[ecs.ID]map[ecs.Entity]int
LevelScore map[int]*Score // one score per level
Id int
MaxLevels int
}
func (observer *GameObserver) GetListenerCallback(comp ecs.ID) listener.Callback {
return listener.NewCallback(
func(world *ecs.World, event ecs.EntityEvent) {
observer.RemoveEntity(event.Entity, comp)
},
event.EntityRemoved,
comp,
)
}
func NewGameObserver(
world *ecs.World, startlevel, width, height, cellsize int) *GameObserver {
observer := &GameObserver{ observer := &GameObserver{
CurrentLevel: startlevel, CurrentLevel: startlevel,
StopTimer: &components.Timer{}, StopTimer: &components.Timer{},
Width: width, Width: width,
Height: height, Height: height,
Cellsize: cellsize, Cellsize: cellsize,
World: world,
Id: rand.Intn(1000),
} }
playerID := ecs.ComponentID[components.Player](world)
obstacleID := ecs.ComponentID[components.Obstacle](world)
particleID := ecs.ComponentID[components.Particle](world)
playerListener := observer.GetListenerCallback(playerID)
obstacleListener := observer.GetListenerCallback(obstacleID)
particleListener := observer.GetListenerCallback(particleID)
listen := listener.NewDispatch(
&playerListener,
&obstacleListener,
&particleListener,
)
world.SetListener(&listen)
observer.Entities = make(map[ecs.ID]map[ecs.Entity]int)
observer.Entities[playerID] = make(map[ecs.Entity]int)
observer.Entities[obstacleID] = make(map[ecs.Entity]int)
observer.Entities[particleID] = make(map[ecs.Entity]int)
resmanger := generic.NewResource[GameObserver](world) resmanger := generic.NewResource[GameObserver](world)
resmanger.Add(observer) resmanger.Add(observer)
@@ -33,11 +83,105 @@ func NewGameObserver(world *ecs.World, startlevel, width, height, cellsize int)
} }
func GetGameObserver(world *ecs.World) *GameObserver { func GetGameObserver(world *ecs.World) *GameObserver {
observerID := ecs.ResourceID[GameObserver](world) resmanger := generic.NewResource[GameObserver](world)
observer := world.Resources().Get(observerID).(*GameObserver) observer := resmanger.Get()
return observer return observer
} }
func (observer *GameObserver) Gameover() { func (observer *GameObserver) Gameover() {
observer.Lost = true observer.Lost = true
} }
func (observer *GameObserver) AddEntity(entity ecs.Entity, comp ecs.ID) {
observer.Entities[comp][entity] = 1
}
func (observer *GameObserver) RemoveEntity(entity ecs.Entity, comp ecs.ID) {
delete(observer.Entities[comp], entity)
}
func (observer *GameObserver) GetEntities(comp ecs.ID) []ecs.Entity {
keys := make([]ecs.Entity, 0, len(observer.Entities[comp]))
for k, _ := range observer.Entities[comp] {
keys = append(keys, k)
}
return keys
}
func (observer *GameObserver) GetPlayers() []ecs.Entity {
playerID := ecs.ComponentID[components.Player](observer.World)
return observer.GetEntities(playerID)
}
func (observer *GameObserver) GetObstacles() []ecs.Entity {
obstacleID := ecs.ComponentID[components.Obstacle](observer.World)
return observer.GetEntities(obstacleID)
}
func (observer *GameObserver) GetParticles() []ecs.Entity {
particleID := ecs.ComponentID[components.Particle](observer.World)
return observer.GetEntities(particleID)
}
func (observer *GameObserver) RemoveEntities() {
playerID := ecs.ComponentID[components.Player](observer.World)
obstacleID := ecs.ComponentID[components.Obstacle](observer.World)
particleID := ecs.ComponentID[components.Particle](observer.World)
observer.Entities = make(map[ecs.ID]map[ecs.Entity]int)
observer.Entities[playerID] = make(map[ecs.Entity]int)
observer.Entities[obstacleID] = make(map[ecs.Entity]int)
observer.Entities[particleID] = make(map[ecs.Entity]int)
}
func (observer *GameObserver) SetupLevelScore(min []int) {
observer.LevelScore = make(map[int]*Score, len(min))
for level, minmoves := range min {
observer.LevelScore[level] = &Score{Min: minmoves}
}
}
func (observer *GameObserver) SetupMaxLevels(count int) {
observer.MaxLevels = count
}
// set current level stats and reset counters
func (observer *GameObserver) AddScore() {
level := observer.CurrentLevel
moves := observer.Moves
slog.Debug("AddScore", "moves", observer.Moves)
if observer.Lost {
observer.LevelScore[level].Score = 0
slog.Debug("lost")
} else {
observer.LevelScore[level].Score = ((observer.LevelScore[level].Min * 100) / moves) / 30
slog.Debug("won", "score", observer.LevelScore[level].Score,
"Min", observer.LevelScore[level].Min,
"Min-x-100", observer.LevelScore[level].Min*100,
"Min-x-100-moves", (observer.LevelScore[level].Min*100)/moves,
)
}
observer.Moves = 0
}
func (observer *GameObserver) AddMove() {
observer.Moves++
}
func (observer *GameObserver) GetScore() int {
sum := 0
for _, score := range observer.LevelScore {
sum += score.Score
}
return sum
}
func (observer *GameObserver) GetLevelScore() int {
return observer.LevelScore[observer.CurrentLevel].Score
}

View File

@@ -1,54 +0,0 @@
package observers
import (
"github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/ecs/event"
"github.com/mlange-42/arche/generic"
"github.com/mlange-42/arche/listener"
)
// will be added as an ecs.Resource to the world so we can use it in
// CollisionSystem to dynamically make it appear or disappear
type ObstacleObserver struct {
// we only have one obstacle so far, if we use multiple ones, turn
// this in to a map, see player_observer.go for an example
Entities map[ecs.Entity]int
}
// Create a new resource of type PlayerObserver which will hold all
// obstacle entities. We also add a Listener which looks for
// EntityRemoved events and if a player gets removed, we also remove
// it from the Entity map in our observer.
func NewObstacleObserver(world *ecs.World) {
observer := &ObstacleObserver{}
observer.Entities = make(map[ecs.Entity]int)
resmanger := generic.NewResource[ObstacleObserver](world)
resmanger.Add(observer)
listen := listener.NewCallback(
func(world *ecs.World, event ecs.EntityEvent) {
observerID := ecs.ResourceID[ObstacleObserver](world)
observer := world.Resources().Get(observerID).(*ObstacleObserver)
observer.RemoveEntity(event.Entity)
},
event.EntityRemoved,
)
world.SetListener(&listen)
}
func GetObstacleObserver(world *ecs.World) *ObstacleObserver {
observerID := ecs.ResourceID[ObstacleObserver](world)
observer := world.Resources().Get(observerID).(*ObstacleObserver)
return observer
}
func (observer *ObstacleObserver) AddEntity(entity ecs.Entity) {
observer.Entities[entity] = 1
}
func (observer *ObstacleObserver) RemoveEntity(entity ecs.Entity) {
observer.Entities = make(map[ecs.Entity]int)
}

View File

@@ -1,53 +0,0 @@
package observers
import (
"github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/ecs/event"
"github.com/mlange-42/arche/generic"
"github.com/mlange-42/arche/listener"
)
// will be added as an ecs.Resource to the world so we can use it in
// CollisionSystem to dynamically make it appear or disappear
type ParticleObserver struct {
// we only have one particle so far, if we use multiple ones, turn
// this in to a map, see player_observer.go for an example
Entity ecs.Entity
}
// Create a new resource of type PlayerObserver which will hold all
// player entities. We also add a Listener which looks for
// EntityRemoved events and if a player gets removed, we also remove
// it from the Entity map in our observer.
func NewParticleObserver(world *ecs.World) {
observer := &ParticleObserver{}
resmanger := generic.NewResource[ParticleObserver](world)
resmanger.Add(observer)
listen := listener.NewCallback(
func(world *ecs.World, event ecs.EntityEvent) {
observerID := ecs.ResourceID[ParticleObserver](world)
observer := world.Resources().Get(observerID).(*ParticleObserver)
observer.RemoveEntity(event.Entity)
},
event.EntityRemoved,
)
world.SetListener(&listen)
}
func GetParticleObserver(world *ecs.World) *ParticleObserver {
observerID := ecs.ResourceID[ParticleObserver](world)
observer := world.Resources().Get(observerID).(*ParticleObserver)
return observer
}
func (observer *ParticleObserver) AddEntity(entity ecs.Entity) {
observer.Entity = entity
}
func (observer *ParticleObserver) RemoveEntity(entity ecs.Entity) {
observer.Entity = ecs.Entity{}
}

View File

@@ -1,56 +0,0 @@
package observers
import (
"github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/ecs/event"
"github.com/mlange-42/arche/generic"
"github.com/mlange-42/arche/listener"
)
// will be added as an ecs.Resource to the world so we can use it in
// CollisionSystem to check for player collisions.
type PlayerObserver struct {
Entities map[ecs.Entity]int
}
// Create a new resource of type PlayerObserver which will hold all
// player entities. We also add a Listener which looks for
// EntityRemoved events and if a player gets removed, we also remove
// it from the Entity map in our observer.
func NewPlayerObserver(world *ecs.World) {
observer := &PlayerObserver{}
observer.Entities = make(map[ecs.Entity]int)
resmanger := generic.NewResource[PlayerObserver](world)
resmanger.Add(observer)
listen := listener.NewCallback(
func(world *ecs.World, event ecs.EntityEvent) {
observerID := ecs.ResourceID[PlayerObserver](world)
observer := world.Resources().Get(observerID).(*PlayerObserver)
observer.RemoveEntity(event.Entity)
},
event.EntityRemoved,
)
world.SetListener(&listen)
}
func GetPlayerObserver(world *ecs.World) *PlayerObserver {
observerID := ecs.ResourceID[PlayerObserver](world)
observer := world.Resources().Get(observerID).(*PlayerObserver)
return observer
}
func (observer *PlayerObserver) AddEntity(entity ecs.Entity) {
observer.Entities[entity] = 1
}
func (observer *PlayerObserver) RemoveEntity(entity ecs.Entity) {
delete(observer.Entities, entity)
}
func (observer *PlayerObserver) RemoveEntities() {
observer.Entities = make(map[ecs.Entity]int)
}

Binary file not shown.

BIN
src/block-grey-damage.xcf Normal file

Binary file not shown.

Binary file not shown.

BIN
src/entities/church_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/entities/emerald_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

BIN
src/entities/relief_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 B

BIN
src/entitymap.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Some files were not shown because too many files have changed in this diff Show More