Compare commits
1 Commits
separate-c
...
collidersy
| Author | SHA1 | Date | |
|---|---|---|---|
| 267e15cc27 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1 @@
|
||||
openquell*
|
||||
openquell
|
||||
|
||||
18
Makefile
18
Makefile
@@ -1,12 +1,3 @@
|
||||
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
|
||||
@echo ok
|
||||
|
||||
@@ -14,13 +5,8 @@ clean:
|
||||
rm -f openquell
|
||||
|
||||
build:
|
||||
go build -ldflags "-X 'openquell/config.VERSION=$(VERSION)'"
|
||||
go build
|
||||
|
||||
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
|
||||
|
||||
test:
|
||||
@echo $(VERSION)
|
||||
@echo 1
|
||||
|
||||
36
TODO.md
36
TODO.md
@@ -2,31 +2,31 @@
|
||||
|
||||
- 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:
|
||||
https://github.com/mlange-42/arche/issues/374
|
||||
|
||||
- Add some final message when the player reaches the last level, start
|
||||
from scratch or a message to buy me some beer, whatever
|
||||
|
||||
- Check player-player collisions!
|
||||
|
||||
- Add player mergers, possibly as an option, so maybe we could have
|
||||
different primary and secondary players: one pair can merge, the
|
||||
other not. Like S + s and M + m or something.
|
||||
|
||||
- Add shaders for animation (player destruction etc)
|
||||
|
||||
- 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.
|
||||
|
||||
- for finding caller:
|
||||
pc := make([]uintptr, 10)
|
||||
n := runtime.Callers(0, pc)
|
||||
pc = pc[:n]
|
||||
fs := runtime.CallersFrames(pc)
|
||||
source, _ := fs.Next()
|
||||
source, _ = fs.Next()
|
||||
source, _ = fs.Next()
|
||||
|
||||
slog.Debug("get observer", "minmoves", observer.LevelScore,
|
||||
"file", source.File, "line", source.Line)
|
||||
|
||||
- Turn menu button in hud_system (events in level_scene!) into ebitenui button
|
||||
- Add player HUD + Stats (as hud_system!)
|
||||
|
||||
|
||||
|
||||
## Collider Rework [abandoned: see branch collider-system, fails]
|
||||
## Collider Rework
|
||||
|
||||
- do not use the map anymore for collision detection
|
||||
- central collision_system
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Description: open the door
|
||||
MinMoves: 2
|
||||
Description: test obstacles
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Description: test obstacles
|
||||
MinMoves: 14
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Description: collect all yellow dots
|
||||
MinMoves: 7
|
||||
Description: find the fruit
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
Description: some enemies can be moved
|
||||
MinMoves: 5
|
||||
Description: win
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
|
||||
########
|
||||
# o <o#
|
||||
#v S # #
|
||||
#v o #
|
||||
# S<# #
|
||||
# #### #
|
||||
|
||||
|
||||
# #### #
|
||||
# #
|
||||
# ^#
|
||||
#> ^#
|
||||
########
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
Description: the more players the better
|
||||
MinMoves: 4
|
||||
Description: use multiple players
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
|
||||
########W#
|
||||
# v#
|
||||
# v #
|
||||
# #
|
||||
#S s#
|
||||
############
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
Description: don't fall off the world
|
||||
MinMoves: 3
|
||||
Description: space
|
||||
Background: background-lila
|
||||
|
||||
|
||||
|
||||
|
||||
W
|
||||
#
|
||||
|
||||
|
||||
S # o
|
||||
S
|
||||
|
||||
|
||||
|
||||
#
|
||||
|
||||
|
||||
o
|
||||
#
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,22 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
_ "image/png"
|
||||
"io/fs"
|
||||
"log"
|
||||
"log/slog"
|
||||
"openquell/config"
|
||||
"openquell/util"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/solarlune/ldtkgo"
|
||||
)
|
||||
|
||||
var Project = LoadLDTK("levels")
|
||||
var Levels = LoadLevels("levels")
|
||||
var Tiles = InitTiles()
|
||||
|
||||
// Tile: contains image, identifier (as used in level data) and
|
||||
@@ -180,22 +185,42 @@ func NewTileHiddenDoor(class []string) *Tile {
|
||||
}
|
||||
|
||||
// used to map level data bytes to actual tiles
|
||||
type TileRegistry map[string]*Tile
|
||||
type TileRegistry map[byte]*Tile
|
||||
|
||||
// holds a raw level spec:
|
||||
//
|
||||
// Name: the name of the level file w/o the .lvl extension
|
||||
// Background: an image name used as game background for this level
|
||||
// Description: text to display on top
|
||||
// Data: a level spec consisting of chars of the above mapping and spaces, e.g.:
|
||||
// ####
|
||||
// # #
|
||||
// ####
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
func InitTiles() TileRegistry {
|
||||
return TileRegistry{
|
||||
"floor": {Id: ' ', Class: "floor", Renderable: false},
|
||||
"default": NewTileBlock("block-greycolored"),
|
||||
"solidorange": NewTileBlock("block-orange-32"),
|
||||
"PlayerPrimary": NewTilePlayer(Primary),
|
||||
"PlayerSecondary": NewTilePlayer(Secondary),
|
||||
"Collectible": NewTileCollectible("collectible-orange"),
|
||||
"ObstacleStar": NewTileObstacle("obstacle-star", config.All),
|
||||
"ObstacleNorth": NewTileObstacle("obstacle-north", config.North),
|
||||
"ObstacleSouth": NewTileObstacle("obstacle-south", config.South),
|
||||
"ObstacleWest": NewTileObstacle("obstacle-west", config.West),
|
||||
"ObstacleEast": NewTileObstacle("obstacle-east", config.East),
|
||||
"Particle": NewTileParticle([]string{
|
||||
' ': {Id: ' ', Class: "floor", Renderable: false},
|
||||
//'#': NewTileBlock("block-grey32"),
|
||||
'#': NewTileBlock("block-greycolored"),
|
||||
'B': NewTileBlock("block-orange-32"),
|
||||
'S': NewTilePlayer(Primary),
|
||||
's': NewTilePlayer(Secondary),
|
||||
'o': NewTileCollectible("collectible-orange"),
|
||||
'+': NewTileObstacle("obstacle-star", config.All),
|
||||
'^': NewTileObstacle("obstacle-north", config.North),
|
||||
'v': NewTileObstacle("obstacle-south", config.South),
|
||||
'<': NewTileObstacle("obstacle-west", config.West),
|
||||
'>': NewTileObstacle("obstacle-east", config.East),
|
||||
'*': NewTileParticle([]string{
|
||||
//"particle-ring-1",
|
||||
"particle-ring-2",
|
||||
"particle-ring-3",
|
||||
@@ -203,56 +228,81 @@ func InitTiles() TileRegistry {
|
||||
"particle-ring-5",
|
||||
"particle-ring-6",
|
||||
}),
|
||||
"Transient": NewTileTranswall([]string{"transwall", "block-orange-32"}),
|
||||
"HiddenDoor": NewTileHiddenDoor([]string{"block-greycolored", "block-greycolored-damaged"}),
|
||||
't': NewTileTranswall([]string{"transwall", "block-orange-32"}),
|
||||
'W': NewTileHiddenDoor([]string{"block-greycolored", "block-greycolored-damaged"}),
|
||||
}
|
||||
}
|
||||
|
||||
// load LDTK project at compile time into ram
|
||||
func LoadLDTK(dir string) *ldtkgo.Project {
|
||||
fd, err := assetfs.Open("levels/openquell.ldtk")
|
||||
// load levels at compile time into ram, creates a slice of raw levels
|
||||
func LoadLevels(dir string) []RawLevel {
|
||||
levels := []RawLevel{}
|
||||
|
||||
// we use embed.FS to iterate over all files in ./levels/
|
||||
entries, err := assetfs.ReadDir(dir)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open LDTK file levels/openquell.ldtk: %s", err)
|
||||
log.Fatalf("failed to read level dir %s: %s", dir, 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()
|
||||
|
||||
fileinfo, err := fd.Stat()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to stat() LDTK file levels/openquell.ldtk: %s", err)
|
||||
}
|
||||
name := strings.TrimSuffix(levelfile.Name(), ".lvl")
|
||||
des := ""
|
||||
background := &ebiten.Image{}
|
||||
data := []byte{}
|
||||
|
||||
filesize := fileinfo.Size()
|
||||
buffer := make([]byte, filesize)
|
||||
scanner := bufio.NewScanner(fd)
|
||||
for scanner.Scan() {
|
||||
// ignore any whitespace
|
||||
line := scanner.Text()
|
||||
|
||||
_, err = fd.Read(buffer)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read bytes from LDTK file levels/openquell.ldtk: %s", err)
|
||||
}
|
||||
// ignore empty lines
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
project, err := ldtkgo.Read(buffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 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++
|
||||
switch {
|
||||
case strings.Contains(line, "Background:"):
|
||||
haveit := strings.Split(line, ": ")
|
||||
if util.Exists(Assets, haveit[1]) {
|
||||
background = Assets[haveit[1]]
|
||||
}
|
||||
}
|
||||
|
||||
if have != need {
|
||||
log.Fatalf("level definition for level %d (%s) invalid: %d missing properties\n required: %s",
|
||||
idx, level.Identifier, need-have, strings.Join(properties, ", "),
|
||||
)
|
||||
case strings.Contains(line, "Description:"):
|
||||
haveit := strings.Split(line, ": ")
|
||||
des = haveit[1]
|
||||
default:
|
||||
// all other non-empty and non-equalsign lines are
|
||||
// level definition matrix data, merge thes into data
|
||||
data = append(data, line+"\n"...)
|
||||
}
|
||||
}
|
||||
|
||||
return project
|
||||
return RawLevel{
|
||||
Name: name,
|
||||
Data: data,
|
||||
Background: background,
|
||||
Description: des,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
_ "image/png"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
// Maps image name to image data
|
||||
type AssetRegistry map[string]*ebiten.Image
|
||||
|
||||
//go:embed sprites/*.png fonts/*.ttf levels/*.ldtk
|
||||
//go:embed levels/*.lvl sprites/*.png fonts/*.ttf
|
||||
var assetfs embed.FS
|
||||
|
||||
var Assets = LoadImages("sprites")
|
||||
@@ -30,8 +31,8 @@ func LoadImages(dir string) AssetRegistry {
|
||||
}
|
||||
|
||||
for _, imagefile := range entries {
|
||||
path := filepath.Join(dir, imagefile.Name())
|
||||
fd, err := assetfs.Open(path)
|
||||
path := filepath.Join("assets", dir, imagefile.Name())
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to open image file %s: %s", imagefile.Name(), err)
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
BIN
assets/sprites/block-grey.png
Normal file
BIN
assets/sprites/block-grey.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 584 B |
@@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
montage -tile 4x0 -geometry +0+0 block* collectible-orange.png obstacle-* sphere-blue* transwall.png map.png && okular map.png
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB |
14
components/collider.go
Normal file
14
components/collider.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package components
|
||||
|
||||
import "github.com/mlange-42/arche/ecs"
|
||||
|
||||
// if the function returns true, the entity will be removed. The
|
||||
// supplied entity is the target entitiy with which the checked one
|
||||
// collided.
|
||||
|
||||
type Collider struct {
|
||||
CollisionHandler func(*ecs.World, *Position, *Position, *Velocity, ecs.Entity) bool
|
||||
EdgeHandler func(*ecs.World, *Position, *Position, *Velocity, ecs.Entity) bool
|
||||
PassoverHandler func(*ecs.World, *Position, *Velocity, *ecs.Entity) bool
|
||||
PassoverFinishedHandler func(*ecs.World, *Position, *Velocity, *ecs.Entity) bool
|
||||
}
|
||||
@@ -5,9 +5,6 @@ import "github.com/hajimehoshi/ebiten/v2"
|
||||
type Player struct {
|
||||
IsPrimary bool
|
||||
Sprites []*ebiten.Image
|
||||
LoopPos *Position
|
||||
LoopStart bool
|
||||
LoopCount int
|
||||
}
|
||||
|
||||
func (player *Player) SwitchSprite() *ebiten.Image {
|
||||
|
||||
@@ -90,7 +90,14 @@ func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *P
|
||||
|
||||
is := tile.Rect.Bounds().Intersect(object.Rect.Bounds())
|
||||
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
|
||||
switch velocity.Direction {
|
||||
case West:
|
||||
@@ -103,13 +110,6 @@ func (tile *Position) Intersects(moving *Position, velocity *Velocity) (bool, *P
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,6 @@ func (velocity *Velocity) InvertDirection() int {
|
||||
return South
|
||||
case All:
|
||||
return Stop
|
||||
case Stop:
|
||||
return Stop
|
||||
}
|
||||
|
||||
// should not happen
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
import "time"
|
||||
|
||||
const (
|
||||
Stop = iota
|
||||
@@ -13,13 +11,6 @@ const (
|
||||
All
|
||||
)
|
||||
|
||||
const PLAYERSPEED int = 5
|
||||
const PLAYERSPEED int = 4
|
||||
const PARTICLE_LOOPWAIT time.Duration = 250 * time.Millisecond
|
||||
const LEVEL_END_WAIT time.Duration = 500 * time.Millisecond
|
||||
const version string = "1.2.0"
|
||||
|
||||
const MenuRectX int = 600
|
||||
const MenuRectY int = 0
|
||||
const MenuRectCellsize int = 32
|
||||
|
||||
var VERSION string // maintained by -x
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"image/color"
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
"openquell/config"
|
||||
"openquell/gameui"
|
||||
|
||||
"github.com/ebitenui/ebitenui"
|
||||
@@ -28,8 +27,7 @@ Thomas von Dein <tom@vondein.org>.
|
||||
Download it on repo.daemon.de/openquell/.
|
||||
|
||||
Copyright (c) 2024 by Thomas von Dein.
|
||||
|
||||
Version: `
|
||||
`
|
||||
|
||||
func NewAboutScene(game *Game) Scene {
|
||||
scene := &AboutScene{Whoami: About, Game: game, Next: About}
|
||||
@@ -83,7 +81,7 @@ func (scene *AboutScene) SetupUI() {
|
||||
)
|
||||
|
||||
about := widget.NewText(
|
||||
widget.TextOpts.Text(ABOUT+config.VERSION, *assets.FontRenderer.FontNormal, blue),
|
||||
widget.TextOpts.Text(ABOUT, *assets.FontRenderer.FontNormal, blue),
|
||||
widget.TextOpts.Position(widget.TextPositionStart, widget.TextPositionCenter),
|
||||
)
|
||||
|
||||
|
||||
36
game/game.go
36
game/game.go
@@ -18,7 +18,7 @@ type Game struct {
|
||||
Scenes map[SceneName]Scene
|
||||
CurrentScene SceneName
|
||||
Observer *observers.GameObserver
|
||||
Levels []*Level // fed in PlayScene.GenerateLevels()
|
||||
Levels []*Level // needed to feed select_scene
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
@@ -35,14 +35,17 @@ func NewGame(width, height, cellsize int, cfg *config.Config, startscene SceneNa
|
||||
Config: cfg,
|
||||
}
|
||||
|
||||
game.Observer = observers.NewGameObserver(
|
||||
&world, cfg.Startlevel, width, height, cellsize)
|
||||
observers.NewPlayerObserver(&world)
|
||||
observers.NewParticleObserver(&world)
|
||||
observers.NewObstacleObserver(&world)
|
||||
observers.NewEntityObserver(&world)
|
||||
game.Observer = observers.NewGameObserver(&world, cfg.Startlevel, width, height, cellsize)
|
||||
|
||||
game.Scenes[Welcome] = NewWelcomeScene(game)
|
||||
game.Scenes[Menu] = NewMenuScene(game)
|
||||
game.Scenes[About] = NewAboutScene(game)
|
||||
game.Scenes[Popup] = NewPopupScene(game)
|
||||
game.Scenes[Play] = NewPlayScene(game, cfg.Startlevel)
|
||||
game.Scenes[Play] = NewLevelScene(game, cfg.Startlevel)
|
||||
game.Scenes[Select] = NewSelectScene(game)
|
||||
|
||||
game.CurrentScene = startscene
|
||||
@@ -57,18 +60,22 @@ func (game *Game) GetCurrentScene() Scene {
|
||||
}
|
||||
|
||||
func (game *Game) Update() error {
|
||||
gameobserver := observers.GetGameObserver(game.World)
|
||||
|
||||
// handle level ends
|
||||
timer := game.Observer.StopTimer
|
||||
timer := gameobserver.StopTimer
|
||||
|
||||
if timer.IsReady() {
|
||||
// a level is either lost or won, we display a small popup
|
||||
// asking the user how to continue from here
|
||||
timer.Reset()
|
||||
|
||||
slog.Debug("timer ready", "lost", game.Observer.Lost, "retry", game.Observer.Retry)
|
||||
game.Observer.AddScore()
|
||||
slog.Debug("timer ready", "lost", gameobserver.Lost, "retry", gameobserver.Retry)
|
||||
if !gameobserver.Lost {
|
||||
gameobserver.Score++ // FIXME: use level.Score(), see TODO
|
||||
}
|
||||
|
||||
game.Scenes[Nextlevel] = NewNextlevelScene(game)
|
||||
game.Scenes[Nextlevel] = NewNextlevelScene(game, gameobserver.Lost)
|
||||
game.CurrentScene = Nextlevel
|
||||
}
|
||||
|
||||
@@ -82,19 +89,18 @@ func (game *Game) Update() error {
|
||||
}
|
||||
|
||||
next := scene.GetNext()
|
||||
|
||||
if next != game.CurrentScene {
|
||||
if next == Play && game.CurrentScene == Nextlevel {
|
||||
// switched from nextlevel (lost or won) popup to play (either retry or next level)
|
||||
if !game.Observer.Retry {
|
||||
game.Observer.CurrentLevel++
|
||||
if !gameobserver.Retry {
|
||||
gameobserver.CurrentLevel++
|
||||
}
|
||||
game.Observer.Retry = false
|
||||
gameobserver.Retry = false
|
||||
}
|
||||
|
||||
if next == Play {
|
||||
// fresh setup of actual level every time we enter the play scene
|
||||
game.Scenes[Play].SetLevel(game.Observer.CurrentLevel)
|
||||
game.Scenes[Play].SetLevel(gameobserver.CurrentLevel)
|
||||
}
|
||||
|
||||
// make sure we stay on the selected scene
|
||||
@@ -103,8 +109,8 @@ func (game *Game) Update() error {
|
||||
// finally switch
|
||||
game.CurrentScene = next
|
||||
|
||||
// FIXME: add some reset function to game.Observer for these kinds of things
|
||||
game.Observer.Lost = false
|
||||
// FIXME: add some reset function to gameobserver for these kinds of things
|
||||
gameobserver.Lost = false
|
||||
}
|
||||
|
||||
timer.Update()
|
||||
|
||||
93
game/level_scene.go
Normal file
93
game/level_scene.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
)
|
||||
|
||||
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) {
|
||||
// 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)
|
||||
|
||||
// FIXME: put into hud_system
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
screen.DrawImage(assets.Assets["hud"], op)
|
||||
ebitenutil.DebugPrintAt(screen, fmt.Sprintf(
|
||||
"FPS: %02.f TPS: %02.f",
|
||||
ebiten.ActualFPS(),
|
||||
ebiten.ActualTPS(),
|
||||
), 10, 10)
|
||||
}
|
||||
110
game/levels.go
110
game/levels.go
@@ -2,17 +2,18 @@ package game
|
||||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
"openquell/grid"
|
||||
"openquell/observers"
|
||||
"openquell/systems"
|
||||
"openquell/util"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
"github.com/solarlune/ldtkgo"
|
||||
)
|
||||
|
||||
type Map map[image.Point]*assets.Tile
|
||||
@@ -23,7 +24,6 @@ type Level struct {
|
||||
World *ecs.World
|
||||
Name string
|
||||
Description string
|
||||
Number int
|
||||
Mapslice Map
|
||||
BackupMapslice Map
|
||||
GridContainer *grid.GridContainer
|
||||
@@ -31,31 +31,30 @@ type Level struct {
|
||||
Grid *grid.Grid
|
||||
}
|
||||
|
||||
func NewLevel(game *Game, cellsize int, plan *ldtkgo.Level) *Level {
|
||||
func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
|
||||
systemlist := []systems.System{}
|
||||
|
||||
gridcontainer := &grid.GridContainer{}
|
||||
|
||||
// FIXME: use plan.BGImage.Path here?
|
||||
systemlist = append(systemlist,
|
||||
systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize,
|
||||
assets.Assets[plan.PropertyByIdentifier("background").AsString()]))
|
||||
systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize, plan.Background))
|
||||
|
||||
systemlist = append(systemlist, systems.NewCollectibleSystem(game.World))
|
||||
|
||||
systemlist = append(systemlist, systems.NewObstacleSystem(game.World, gridcontainer))
|
||||
systemlist = append(systemlist,
|
||||
systems.NewCollisionSystem(game.World, gridcontainer))
|
||||
|
||||
systemlist = append(systemlist,
|
||||
systems.NewPlayerSystem(game.World, gridcontainer, game.ScreenWidth, game.ScreenHeight))
|
||||
systems.NewPlayerSystem(game.World, gridcontainer))
|
||||
|
||||
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.NewDestroyableSystem(game.World, gridcontainer))
|
||||
|
||||
systemlist = append(systemlist, systems.NewHudSystem(game.World, plan))
|
||||
|
||||
mapslice, backupmap := LevelToSlice(game, plan, cellsize)
|
||||
|
||||
return &Level{
|
||||
@@ -65,9 +64,8 @@ func NewLevel(game *Game, cellsize int, plan *ldtkgo.Level) *Level {
|
||||
World: game.World,
|
||||
Width: game.ScreenWidth,
|
||||
Height: game.ScreenHeight,
|
||||
Description: plan.PropertyByIdentifier("description").AsString(),
|
||||
Number: plan.PropertyByIdentifier("level").AsInt(),
|
||||
Name: strings.ReplaceAll(plan.Identifier, "_", " "),
|
||||
Description: plan.Description,
|
||||
Name: plan.Name,
|
||||
GridContainer: gridcontainer,
|
||||
Systems: systemlist,
|
||||
}
|
||||
@@ -85,6 +83,13 @@ func (level *Level) Draw(screen *ebiten.Image) {
|
||||
}
|
||||
}
|
||||
|
||||
func (level *Level) Position2Point(position *components.Position) image.Point {
|
||||
return image.Point{
|
||||
int(position.X) / level.Cellsize,
|
||||
int(position.Y) / level.Cellsize,
|
||||
}
|
||||
}
|
||||
|
||||
func (level *Level) RestoreMap() {
|
||||
for point, tile := range level.BackupMapslice {
|
||||
level.Mapslice[point] = tile.Clone()
|
||||
@@ -103,8 +108,8 @@ func (level *Level) SetupGrid(game *Game) {
|
||||
level.World.Batch().RemoveEntities(selector)
|
||||
|
||||
// get rid of any players on PlayerObserver. FIXME: remove them in grid.NewGrid()?
|
||||
observer := observers.GetGameObserver(level.World)
|
||||
observer.RemoveEntities()
|
||||
playerobserver := observers.GetPlayerObserver(level.World)
|
||||
playerobserver.RemoveEntities()
|
||||
|
||||
// get rid of possibly manipulated map
|
||||
level.RestoreMap()
|
||||
@@ -115,75 +120,26 @@ func (level *Level) SetupGrid(game *Game) {
|
||||
grid.NewGrid(game.World, level.Cellsize, level.Width, level.Height, level.Mapslice))
|
||||
}
|
||||
|
||||
func NewMapSlice(game *Game, tilesize int) Map {
|
||||
// parses a RawLevel and generates a mapslice from it, which is being used as grid
|
||||
func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) (Map, Map) {
|
||||
size := game.ScreenWidth * game.ScreenHeight
|
||||
mapslice := make(Map, size)
|
||||
backupmap := make(Map, size)
|
||||
|
||||
for y := 0; y < game.ScreenHeight; y += 32 {
|
||||
for x := 0; x < game.ScreenWidth; x += 32 {
|
||||
mapslice[image.Point{x / tilesize, y / tilesize}] = assets.Tiles["floor"]
|
||||
for y, line := range strings.Split(string(level.Data), "\n") {
|
||||
if len(line) != game.ScreenWidth/tilesize && y < game.ScreenHeight/tilesize {
|
||||
log.Fatalf("line %d doesn't contain %d tiles, but %d",
|
||||
y, game.ScreenWidth/tilesize, len(line))
|
||||
}
|
||||
}
|
||||
|
||||
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"]
|
||||
|
||||
tile.Sprite = assets.Assets["map"].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()
|
||||
}
|
||||
for x, char := range line {
|
||||
if !util.Exists(assets.Tiles, byte(char)) {
|
||||
log.Fatalf("unregistered tile type %c encountered", char)
|
||||
}
|
||||
|
||||
case ldtkgo.LayerTypeEntity:
|
||||
// load mobile tiles (they call them entities) using static map map.png.
|
||||
tileset := assets.Assets["map"]
|
||||
|
||||
for _, entity := range layer.Entities {
|
||||
if entity.TileRect != nil {
|
||||
tile := assets.Tiles[entity.Identifier]
|
||||
|
||||
tileRect := entity.TileRect
|
||||
//slog.Debug("tilerect", "x", tileRect.X, "y", tileRect.Y, "which", entity.Identifier)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
tile := assets.Tiles[byte(char)]
|
||||
mapslice[image.Point{x, y}] = tile
|
||||
backupmap[image.Point{x, y}] = tile.Clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
@@ -23,11 +22,12 @@ type NextlevelScene struct {
|
||||
Lost bool
|
||||
}
|
||||
|
||||
func NewNextlevelScene(game *Game) Scene {
|
||||
func NewNextlevelScene(game *Game, lost bool) Scene {
|
||||
scene := &NextlevelScene{
|
||||
Whoami: Nextlevel,
|
||||
Game: game,
|
||||
Next: Nextlevel,
|
||||
Lost: lost,
|
||||
}
|
||||
|
||||
scene.SetupUI()
|
||||
@@ -61,7 +61,7 @@ func (scene *NextlevelScene) Update() error {
|
||||
func (scene *NextlevelScene) SetLevel(level int) {}
|
||||
|
||||
func (scene *NextlevelScene) Draw(screen *ebiten.Image) {
|
||||
background := assets.Assets["background-popup-wide"]
|
||||
background := assets.Assets["background-popup"]
|
||||
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Translate(
|
||||
@@ -69,81 +69,34 @@ func (scene *NextlevelScene) Draw(screen *ebiten.Image) {
|
||||
float64((scene.Game.ScreenHeight/2)-(background.Bounds().Dy()/2)),
|
||||
)
|
||||
|
||||
screen.DrawImage(background, op)
|
||||
screen.DrawImage(assets.Assets["background-popup"], op)
|
||||
|
||||
scene.Ui.Draw(screen)
|
||||
}
|
||||
|
||||
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}
|
||||
observer := observers.GetGameObserver(scene.Game.World)
|
||||
gameobserver := observers.GetGameObserver(scene.Game.World)
|
||||
|
||||
rowContainer := gameui.NewRowContainer(false)
|
||||
label1text := "Last level, congratulations!"
|
||||
label2text := fmt.Sprintf("Your final score: %d", observer.GetScore())
|
||||
labeltext := "Success"
|
||||
|
||||
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 {
|
||||
switch scene.Lost {
|
||||
case true:
|
||||
labeltext = "Too bad!"
|
||||
labeltext = "Failure"
|
||||
}
|
||||
|
||||
buttonRetry := gameui.NewMenuButton("Retry", *assets.FontRenderer.FontNormal,
|
||||
func(args *widget.ButtonClickedEventArgs) {
|
||||
scene.SetNext(Play)
|
||||
observer.Retry = true
|
||||
gameobserver.Retry = true
|
||||
})
|
||||
|
||||
if !islast {
|
||||
buttonNext = gameui.NewMenuButton("Next Level", *assets.FontRenderer.FontNormal,
|
||||
func(args *widget.ButtonClickedEventArgs) {
|
||||
scene.SetNext(Play)
|
||||
observer.Retry = false
|
||||
})
|
||||
}
|
||||
buttonNext := gameui.NewMenuButton("Next Level", *assets.FontRenderer.FontNormal,
|
||||
func(args *widget.ButtonClickedEventArgs) {
|
||||
scene.SetNext(Play)
|
||||
gameobserver.Retry = false
|
||||
})
|
||||
|
||||
buttonAbort := gameui.NewMenuButton("Abort", *assets.FontRenderer.FontNormal,
|
||||
func(args *widget.ButtonClickedEventArgs) {
|
||||
@@ -151,14 +104,12 @@ func (scene *NextlevelScene) SetupUIRetry() {
|
||||
})
|
||||
|
||||
label := widget.NewText(
|
||||
widget.TextOpts.Text(labeltext, *assets.FontRenderer.FontNormal, blue),
|
||||
widget.TextOpts.Text(labeltext, *assets.FontRenderer.FontBig, blue),
|
||||
widget.TextOpts.Position(widget.TextPositionCenter, widget.TextPositionCenter),
|
||||
)
|
||||
|
||||
rowContainer.AddChild(label)
|
||||
if !islast {
|
||||
rowContainer.AddChild(buttonNext)
|
||||
}
|
||||
rowContainer.AddChild(buttonNext)
|
||||
rowContainer.AddChild(buttonRetry)
|
||||
rowContainer.AddChild(buttonAbort)
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
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)
|
||||
}
|
||||
@@ -5,13 +5,11 @@ import (
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
"openquell/gameui"
|
||||
"openquell/observers"
|
||||
|
||||
"github.com/ebitenui/ebitenui"
|
||||
"github.com/ebitenui/ebitenui/widget"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
type PopupScene struct {
|
||||
@@ -52,11 +50,6 @@ func (scene *PopupScene) Clearscreen() bool {
|
||||
|
||||
func (scene *PopupScene) Update() error {
|
||||
scene.Ui.Update()
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||
scene.SetNext(Play)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -76,7 +69,6 @@ func (scene *PopupScene) Draw(screen *ebiten.Image) {
|
||||
|
||||
func (scene *PopupScene) SetupUI() {
|
||||
blue := color.RGBA{0, 255, 128, 255}
|
||||
observer := observers.GetGameObserver(scene.Game.World)
|
||||
|
||||
rowContainer := gameui.NewRowContainer(false)
|
||||
|
||||
@@ -90,10 +82,9 @@ func (scene *PopupScene) SetupUI() {
|
||||
scene.SetNext(Menu)
|
||||
})
|
||||
|
||||
buttonRetry := gameui.NewMenuButton("Retry", *assets.FontRenderer.FontNormal,
|
||||
buttonOptions := gameui.NewMenuButton("Options", *assets.FontRenderer.FontNormal,
|
||||
func(args *widget.ButtonClickedEventArgs) {
|
||||
scene.SetNext(Play)
|
||||
observer.Retry = true
|
||||
scene.SetNext(Settings)
|
||||
})
|
||||
|
||||
label := widget.NewText(
|
||||
@@ -103,8 +94,8 @@ func (scene *PopupScene) SetupUI() {
|
||||
|
||||
rowContainer.AddChild(label)
|
||||
rowContainer.AddChild(buttonContinue)
|
||||
rowContainer.AddChild(buttonRetry)
|
||||
rowContainer.AddChild(buttonAbort)
|
||||
rowContainer.AddChild(buttonOptions)
|
||||
|
||||
scene.Ui = &ebitenui.UI{
|
||||
Container: rowContainer.Container(),
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/ebitenui/ebitenui/widget"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
)
|
||||
|
||||
type SelectScene struct {
|
||||
@@ -54,11 +53,6 @@ func (scene *SelectScene) Clearscreen() bool {
|
||||
|
||||
func (scene *SelectScene) Update() error {
|
||||
scene.Ui.Update()
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
|
||||
scene.SetNext(Menu)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -67,9 +61,8 @@ func (scene *SelectScene) Draw(screen *ebiten.Image) {
|
||||
}
|
||||
|
||||
type LevelEntry struct {
|
||||
Id int
|
||||
Number int
|
||||
Name string
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
func (scene *SelectScene) SetupUI() {
|
||||
@@ -86,7 +79,7 @@ func (scene *SelectScene) SetupUI() {
|
||||
|
||||
levels := make([]any, 0, len(scene.Game.Levels))
|
||||
for id := 0; id < len(scene.Game.Levels); id++ {
|
||||
levels = append(levels, LevelEntry{Id: id, Number: scene.Game.Levels[id].Number, Name: scene.Game.Levels[id].Name})
|
||||
levels = append(levels, LevelEntry{Id: id, Name: scene.Game.Levels[id].Name})
|
||||
}
|
||||
|
||||
slog.Debug("levels", "levels", levels)
|
||||
@@ -99,10 +92,10 @@ func (scene *SelectScene) SetupUI() {
|
||||
// Set how wide the list should be
|
||||
widget.ListOpts.ContainerOpts(widget.ContainerOpts.WidgetOpts(
|
||||
widget.WidgetOpts.MinSize(150, 0),
|
||||
widget.WidgetOpts.LayoutData(widget.RowLayoutData{
|
||||
Position: widget.RowLayoutPositionCenter,
|
||||
Stretch: true,
|
||||
MaxHeight: 200,
|
||||
widget.WidgetOpts.LayoutData(widget.AnchorLayoutData{
|
||||
HorizontalPosition: widget.AnchorLayoutPositionCenter,
|
||||
VerticalPosition: widget.AnchorLayoutPositionEnd,
|
||||
StretchVertical: true,
|
||||
}),
|
||||
)),
|
||||
// Set the entries in the list
|
||||
@@ -130,18 +123,18 @@ func (scene *SelectScene) SetupUI() {
|
||||
widget.ListOpts.EntryFontFace(*assets.FontRenderer.FontNormal),
|
||||
// Set the colors for the list
|
||||
widget.ListOpts.EntryColor(&widget.ListEntryColor{
|
||||
Selected: color.NRGBA{0, 255, 0, 255},
|
||||
Unselected: color.NRGBA{254, 255, 255, 255},
|
||||
SelectedBackground: color.NRGBA{R: 130, G: 130, B: 200, A: 255},
|
||||
SelectedFocusedBackground: color.NRGBA{R: 130, G: 130, B: 170, A: 255},
|
||||
FocusedBackground: color.NRGBA{R: 170, G: 170, B: 180, A: 255},
|
||||
DisabledUnselected: color.NRGBA{100, 100, 100, 255},
|
||||
DisabledSelected: color.NRGBA{100, 100, 100, 255},
|
||||
DisabledSelectedBackground: color.NRGBA{100, 100, 100, 255},
|
||||
Selected: color.NRGBA{0, 255, 0, 255}, // Foreground color for the unfocused selected entry
|
||||
Unselected: color.NRGBA{254, 255, 255, 255}, // Foreground color for the unfocused unselected entry
|
||||
SelectedBackground: color.NRGBA{R: 130, G: 130, B: 200, A: 255}, // Background color for the unfocused selected entry
|
||||
SelectedFocusedBackground: color.NRGBA{R: 130, G: 130, B: 170, A: 255}, // Background color for the focused selected entry
|
||||
FocusedBackground: color.NRGBA{R: 170, G: 170, B: 180, A: 255}, // Background color for the focused unselected entry
|
||||
DisabledUnselected: color.NRGBA{100, 100, 100, 255}, // Foreground color for the disabled unselected entry
|
||||
DisabledSelected: color.NRGBA{100, 100, 100, 255}, // Foreground color for the disabled selected entry
|
||||
DisabledSelectedBackground: color.NRGBA{100, 100, 100, 255}, // Background color for the disabled selected entry
|
||||
}),
|
||||
// This required function returns the string displayed in the list
|
||||
widget.ListOpts.EntryLabelFunc(func(e interface{}) string {
|
||||
return fmt.Sprintf("%02d %s", e.(LevelEntry).Number, e.(LevelEntry).Name)
|
||||
return e.(LevelEntry).Name
|
||||
}),
|
||||
// Padding for each entry
|
||||
widget.ListOpts.EntryTextPadding(widget.NewInsetsSimple(5)),
|
||||
@@ -155,31 +148,14 @@ 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,
|
||||
func(args *widget.ButtonClickedEventArgs) {
|
||||
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(list)
|
||||
rowContainer.AddChild(buttonContainer)
|
||||
rowContainer.AddChild(buttonPlay)
|
||||
|
||||
scene.Ui = &ebitenui.UI{
|
||||
Container: rowContainer.Container(),
|
||||
|
||||
39
go.mod
39
go.mod
@@ -3,37 +3,34 @@ module openquell
|
||||
go 1.21
|
||||
|
||||
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/knadh/koanf v1.5.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 (
|
||||
github.com/ebitengine/purego v0.6.1 // indirect
|
||||
github.com/alecthomas/repr v0.3.0 // 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/jezek/xgb v1.1.1 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // 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/knadh/koanf v1.5.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/tidwall/gjson v1.9.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // 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
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tinne26/etxt v0.0.8 // indirect
|
||||
github.com/tlinden/yadu v0.1.3 // indirect
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 // indirect
|
||||
golang.org/x/image v0.12.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20230922142353-e2f452493d57 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
133
go.sum
133
go.sum
@@ -1,6 +1,7 @@
|
||||
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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
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/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -35,13 +36,14 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
|
||||
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/ebitengine/purego v0.5.0 h1:JrMGKfRIAM4/QVKaesIIT7m/UVjTj5GYhRSQYwfVdpo=
|
||||
github.com/ebitengine/purego v0.5.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/ebitengine/purego v0.6.0 h1:Yo9uBc1x+ETQbfEaf6wcBsjrQfCEnh/gaGUg7lguEJY=
|
||||
github.com/ebitengine/purego v0.6.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/ebitenui/ebitenui v0.5.5 h1:L9UCWmiMlo4sG5TavQKmjfsnwMmYqkld2tXWZMmKkSA=
|
||||
github.com/ebitenui/ebitenui v0.5.5/go.mod h1:CkzAwu9Ks32P+NC/7+iypdLA85Wqnn93UztPFE+kAH4=
|
||||
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=
|
||||
@@ -52,9 +54,11 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
||||
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/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
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=
|
||||
@@ -65,6 +69,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||
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/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
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=
|
||||
@@ -100,10 +105,17 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
||||
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/bitmapfont v1.3.0/go.mod h1:/Qb7yVjHYNUV4JdqNkPs6BSZwLjKqkZOMIp6jZD0KgE=
|
||||
github.com/hajimehoshi/ebiten v1.12.12 h1:JvmF1bXRa+t+/CcLWxrJCRsdjs2GyBYBSiFAfIqDFlI=
|
||||
github.com/hajimehoshi/ebiten v1.12.12/go.mod h1:1XI25ImVCDPJiXox4h9yK/CvN5sjDYnbF4oZcFzPXHw=
|
||||
github.com/hajimehoshi/ebiten/v2 v2.6.5 h1:lALv+qhEK3CBWViyiGpz4YcR6slVJEjCiS7sExKZ9OE=
|
||||
github.com/hajimehoshi/ebiten/v2 v2.6.5/go.mod h1:TZtorL713an00UW4LyvMeKD8uXWnuIuCPtlH11b0pgI=
|
||||
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/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -128,7 +140,6 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||
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=
|
||||
@@ -138,13 +149,14 @@ github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoI
|
||||
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/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
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=
|
||||
@@ -161,13 +173,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
|
||||
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/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
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=
|
||||
@@ -208,17 +215,17 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
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/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||
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=
|
||||
@@ -239,8 +246,6 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
||||
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=
|
||||
@@ -248,34 +253,25 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
||||
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/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/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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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=
|
||||
@@ -284,26 +280,41 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i
|
||||
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-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-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-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/exp/shiny v0.0.0-20240222234643-814bf88cf225 h1:5c1vh6Z0LHEVurVuFE5ElIYhjVG+nP7ZGFB3yx9yTVA=
|
||||
golang.org/x/exp/shiny v0.0.0-20240222234643-814bf88cf225/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
|
||||
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63 h1:3AGKexOYqL+ztdWdkB1bDwXgPBuTS/S8A4WzuTvJ8Cg=
|
||||
golang.org/x/exp/shiny v0.0.0-20230817173708-d852ddb80c63/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20240213143359-d1f7d3436075 h1:iZzqyDd8gFkJZpsJNzveyScRBcQlsveheh6Q77LzhPY=
|
||||
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.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.4.2/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.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=
|
||||
@@ -321,6 +332,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
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=
|
||||
@@ -330,11 +343,14 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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-20190911185100-cd5d95a43a6e/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-20201020160332-67f06af15bc9/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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
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=
|
||||
@@ -342,9 +358,11 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
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-20190222072716-a9d3bda3a223/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-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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/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=
|
||||
@@ -357,6 +375,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/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-20200918174421-af09f7315aff/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -365,31 +384,46 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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-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.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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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-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-20190312151545-0bb0c0a6e846/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-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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -425,8 +459,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
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 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/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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=
|
||||
|
||||
@@ -5,36 +5,6 @@ import (
|
||||
. "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(
|
||||
position *components.Position,
|
||||
velocity *components.Velocity,
|
||||
|
||||
29
grid/grid.go
29
grid/grid.go
@@ -6,6 +6,7 @@ import (
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
"openquell/config"
|
||||
"openquell/handlers"
|
||||
"openquell/observers"
|
||||
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
@@ -29,11 +30,12 @@ func NewGrid(world *ecs.World,
|
||||
|
||||
// we use this to turn our tiles into iterable entities, used for
|
||||
// collision detection, transformation and other things
|
||||
playermapper := generic.NewMap4[
|
||||
playermapper := generic.NewMap5[
|
||||
components.Position,
|
||||
components.Velocity,
|
||||
components.Renderable,
|
||||
components.Player](world)
|
||||
components.Player,
|
||||
components.Collider](world)
|
||||
|
||||
solidmapper := generic.NewMap4[
|
||||
components.Position,
|
||||
@@ -70,11 +72,11 @@ func NewGrid(world *ecs.World,
|
||||
var transient *components.Transient
|
||||
var player *components.Player
|
||||
var destroyable *components.Destroyable
|
||||
var collider *components.Collider
|
||||
|
||||
playerID := ecs.ComponentID[components.Player](world)
|
||||
obstacleID := ecs.ComponentID[components.Obstacle](world)
|
||||
|
||||
observer := observers.GetGameObserver(world)
|
||||
playerobserver := observers.GetPlayerObserver(world)
|
||||
obstacleobserver := observers.GetObstacleObserver(world)
|
||||
entityobserver := observers.GetEntityObserver(world)
|
||||
|
||||
for point, tile := range mapslice {
|
||||
switch tile.Renderable {
|
||||
@@ -85,30 +87,35 @@ func NewGrid(world *ecs.World,
|
||||
pos, render, _, _ = solidmapper.Get(entity)
|
||||
case tile.Player:
|
||||
entity := playermapper.New()
|
||||
pos, vel, render, player = playermapper.Get(entity)
|
||||
observer.AddEntity(entity, playerID)
|
||||
pos, vel, render, player, collider = playermapper.Get(entity)
|
||||
playerobserver.AddEntity(entity)
|
||||
vel.Speed = config.PLAYERSPEED
|
||||
player.IsPrimary = tile.IsPrimary
|
||||
player.Sprites = tile.Tiles
|
||||
player.LoopPos = &components.Position{Cellsize: tilesize}
|
||||
collider.CollisionHandler = handlers.HandlePlayerCollision
|
||||
collider.EdgeHandler = handlers.HandlePlayerEdgeBump
|
||||
case tile.Collectible:
|
||||
entity := colmapper.New()
|
||||
pos, render, _ = colmapper.Get(entity)
|
||||
entityobserver.AddEntity(entity)
|
||||
case tile.Obstacle:
|
||||
entity := obsmapper.New()
|
||||
pos, vel, render, _ = obsmapper.Get(entity)
|
||||
vel.Direction = tile.Direction
|
||||
vel.PointingAt = tile.Direction
|
||||
vel.Speed = config.PLAYERSPEED
|
||||
observer.AddEntity(entity, obstacleID)
|
||||
obstacleobserver.AddEntity(entity)
|
||||
entityobserver.AddEntity(entity)
|
||||
case tile.Transient:
|
||||
entity := transmapper.New()
|
||||
pos, render, transient = transmapper.Get(entity)
|
||||
transient.Sprites = tile.TileNames
|
||||
entityobserver.AddEntity(entity)
|
||||
case tile.Destroyable:
|
||||
entity := doormapper.New()
|
||||
pos, render, destroyable = doormapper.Get(entity)
|
||||
destroyable.Sprites = tile.Tiles
|
||||
entityobserver.AddEntity(entity)
|
||||
default:
|
||||
log.Fatalln("unsupported tile type encountered")
|
||||
}
|
||||
@@ -158,7 +165,7 @@ func (grid *Grid) RemoveTile(point image.Point) {
|
||||
}
|
||||
|
||||
func (grid *Grid) SetFloorTile(point image.Point) {
|
||||
grid.Map[point] = assets.Tiles["floor"]
|
||||
grid.Map[point] = assets.Tiles[' ']
|
||||
}
|
||||
|
||||
func (grid *Grid) SetSolidTile(tile *assets.Tile, point image.Point) {
|
||||
|
||||
25
handlers/collectible_handler.go
Normal file
25
handlers/collectible_handler.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"openquell/components"
|
||||
. "openquell/config"
|
||||
)
|
||||
|
||||
// type Collider struct {
|
||||
// CollisionHandler func(*Position, *Position, *Velocity) bool
|
||||
// EdgeHandler func(*Position, *Position, *Velocity) bool
|
||||
// PassoverHandler func(*Position, *Velocity) bool
|
||||
// PassoverFinishedHandler func(*Position, *Velocity) bool
|
||||
// }
|
||||
|
||||
func HandleCollectibleCollision(pos, newpos *components.Position, velocity *components.Velocity) bool {
|
||||
pos.Set(newpos)
|
||||
velocity.Change(Stop)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func HandleCollectibleEdgeBump(pos, newpos *components.Position, velocity *components.Velocity) bool {
|
||||
pos.Set(newpos)
|
||||
return true
|
||||
}
|
||||
72
handlers/player_handler.go
Normal file
72
handlers/player_handler.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"openquell/components"
|
||||
. "openquell/config"
|
||||
"openquell/util"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
"github.com/mlange-42/arche/generic"
|
||||
)
|
||||
|
||||
// type Collider struct {
|
||||
// CollisionHandler func(*Position, *Position, *Velocity) bool
|
||||
// EdgeHandler func(*Position, *Position, *Velocity) bool
|
||||
// PassoverHandler func(*Position, *Velocity) bool
|
||||
// PassoverFinishedHandler func(*Position, *Velocity) bool
|
||||
// }
|
||||
|
||||
func HandlePlayerCollision(world *ecs.World, pos, newpos *components.Position,
|
||||
velocity *components.Velocity, target ecs.Entity) bool {
|
||||
|
||||
velmapper := generic.NewMap[components.Velocity](world)
|
||||
wallmapper := generic.NewMap[components.Solid](world)
|
||||
obsmapper := generic.NewMap[components.Obstacle](world)
|
||||
|
||||
if wallmapper.Has(target) {
|
||||
pos.Set(newpos)
|
||||
velocity.Change(Stop)
|
||||
return true
|
||||
}
|
||||
|
||||
if obsmapper.Has(target) {
|
||||
obsvelocity := velmapper.Get(target)
|
||||
|
||||
if CheckObstacleSide(velocity, obsvelocity.Direction) {
|
||||
// player died
|
||||
return false
|
||||
} else {
|
||||
// bumped into nonlethal obstacle side, stop the
|
||||
// player, set the obstacle in motion, if possible
|
||||
obsvelocity.Set(velocity)
|
||||
velocity.Change(Stop)
|
||||
pos.Set(newpos)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func HandlePlayerEdgeBump(world *ecs.World, playerpos,
|
||||
newpos *components.Position, velocity *components.Velocity, target ecs.Entity) bool {
|
||||
playerpos.Set(newpos)
|
||||
return true
|
||||
}
|
||||
|
||||
func CheckObstacleSide(playervelocity *components.Velocity, obsdirection int) bool {
|
||||
movingdirection := playervelocity.InvertDirection()
|
||||
|
||||
if movingdirection == Stop || movingdirection == obsdirection {
|
||||
slog.Debug("Damaging obstacle collision",
|
||||
"playerdirection", util.DirectionStr(playervelocity.Direction),
|
||||
"obsdirection", util.DirectionStr(obsdirection),
|
||||
"movingdirection", util.DirectionStr(movingdirection),
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
16
index.html
16
index.html
@@ -1,16 +0,0 @@
|
||||
<!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>
|
||||
18
level-mode.el
Normal file
18
level-mode.el
Normal file
@@ -0,0 +1,18 @@
|
||||
(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)
|
||||
@@ -1,2 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<iframe src="main.html" width="640" height="480"></iframe>
|
||||
50
observers/entity_observer.go
Normal file
50
observers/entity_observer.go
Normal file
@@ -0,0 +1,50 @@
|
||||
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 EntityObserver 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
|
||||
}
|
||||
|
||||
func NewEntityObserver(world *ecs.World) {
|
||||
observer := &EntityObserver{}
|
||||
observer.Entities = make(map[ecs.Entity]int)
|
||||
|
||||
resmanger := generic.NewResource[EntityObserver](world)
|
||||
resmanger.Add(observer)
|
||||
|
||||
listen := listener.NewCallback(
|
||||
func(world *ecs.World, event ecs.EntityEvent) {
|
||||
observerID := ecs.ResourceID[EntityObserver](world)
|
||||
observer := world.Resources().Get(observerID).(*EntityObserver)
|
||||
observer.RemoveEntity(event.Entity)
|
||||
},
|
||||
|
||||
event.EntityRemoved,
|
||||
)
|
||||
|
||||
world.SetListener(&listen)
|
||||
}
|
||||
|
||||
func GetEntityObserver(world *ecs.World) *EntityObserver {
|
||||
observerID := ecs.ResourceID[EntityObserver](world)
|
||||
observer := world.Resources().Get(observerID).(*EntityObserver)
|
||||
return observer
|
||||
}
|
||||
|
||||
func (observer *EntityObserver) AddEntity(entity ecs.Entity) {
|
||||
observer.Entities[entity] = 1
|
||||
}
|
||||
|
||||
func (observer *EntityObserver) RemoveEntity(entity ecs.Entity) {
|
||||
observer.Entities = make(map[ecs.Entity]int)
|
||||
}
|
||||
@@ -1,81 +1,31 @@
|
||||
package observers
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"openquell/components"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type Score struct {
|
||||
Min, Score int
|
||||
}
|
||||
|
||||
// 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
|
||||
// Used for global game state
|
||||
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
|
||||
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 (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 {
|
||||
func NewGameObserver(world *ecs.World, startlevel, width, height, cellsize int) *GameObserver {
|
||||
observer := &GameObserver{
|
||||
CurrentLevel: startlevel,
|
||||
StopTimer: &components.Timer{},
|
||||
Width: width,
|
||||
Height: height,
|
||||
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.Add(observer)
|
||||
|
||||
@@ -83,105 +33,11 @@ func NewGameObserver(
|
||||
}
|
||||
|
||||
func GetGameObserver(world *ecs.World) *GameObserver {
|
||||
resmanger := generic.NewResource[GameObserver](world)
|
||||
observer := resmanger.Get()
|
||||
|
||||
observerID := ecs.ResourceID[GameObserver](world)
|
||||
observer := world.Resources().Get(observerID).(*GameObserver)
|
||||
return observer
|
||||
}
|
||||
|
||||
func (observer *GameObserver) Gameover() {
|
||||
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
|
||||
}
|
||||
|
||||
54
observers/obstacle_observer.go
Normal file
54
observers/obstacle_observer.go
Normal file
@@ -0,0 +1,54 @@
|
||||
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)
|
||||
}
|
||||
53
observers/particle_observer.go
Normal file
53
observers/particle_observer.go
Normal file
@@ -0,0 +1,53 @@
|
||||
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{}
|
||||
}
|
||||
56
observers/player_observer.go
Normal file
56
observers/player_observer.go
Normal file
@@ -0,0 +1,56 @@
|
||||
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.
@@ -1,6 +1,7 @@
|
||||
package systems
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
. "openquell/components"
|
||||
@@ -28,7 +29,8 @@ func NewCollectibleSystem(world *ecs.World) System {
|
||||
}
|
||||
|
||||
func (system *CollectibleSystem) Update() error {
|
||||
observer := observers.GetGameObserver(system.World)
|
||||
playerobserver := observers.GetPlayerObserver(system.World)
|
||||
gameobserver := observers.GetGameObserver(system.World)
|
||||
|
||||
posID := ecs.ComponentID[components.Position](system.World)
|
||||
veloID := ecs.ComponentID[components.Velocity](system.World)
|
||||
@@ -39,25 +41,21 @@ func (system *CollectibleSystem) Update() error {
|
||||
query := system.Selector.Query(system.World)
|
||||
numcollectibles := query.Count()
|
||||
|
||||
if numcollectibles == 0 || observer.Lost {
|
||||
if numcollectibles == 0 || gameobserver.Lost {
|
||||
query.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
for query.Next() {
|
||||
colposition, _, _ := query.Get()
|
||||
|
||||
for _, player := range observer.GetPlayers() {
|
||||
if !system.World.Alive(player) {
|
||||
continue
|
||||
}
|
||||
colposition, collectible, _ := query.Get()
|
||||
|
||||
for player := range playerobserver.Entities {
|
||||
playerposition := (*Position)(system.World.Get(player, posID))
|
||||
playervelocity := (*Velocity)(system.World.Get(player, veloID))
|
||||
|
||||
ok, _ := colposition.Intersects(playerposition, playervelocity)
|
||||
if ok {
|
||||
//slog.Debug("bumped into collectible", "collectible", collectible)
|
||||
slog.Debug("bumped into collectible", "collectible", collectible)
|
||||
particlepositions = append(particlepositions, colposition)
|
||||
EntitiesToRemove = append(EntitiesToRemove, query.Entity())
|
||||
}
|
||||
@@ -100,7 +98,7 @@ func (system *CollectibleSystem) Draw(screen *ebiten.Image) {
|
||||
}
|
||||
|
||||
func (system *CollectibleSystem) AddParticle(position *components.Position) {
|
||||
observer := observers.GetGameObserver(system.World)
|
||||
particleobserver := observers.GetParticleObserver(system.World)
|
||||
|
||||
ptmapper := generic.NewMap3[
|
||||
components.Position,
|
||||
@@ -108,14 +106,12 @@ func (system *CollectibleSystem) AddParticle(position *components.Position) {
|
||||
components.Timer,
|
||||
](system.World)
|
||||
|
||||
particleID := ecs.ComponentID[components.Particle](system.World)
|
||||
|
||||
entity := ptmapper.New()
|
||||
pos, particle, timer := ptmapper.Get(entity)
|
||||
observer.AddEntity(entity, particleID)
|
||||
particleobserver.AddEntity(entity)
|
||||
|
||||
particle.Index = assets.Tiles["Particle"].Particle
|
||||
particle.Tiles = assets.Tiles["Particle"].Tiles
|
||||
particle.Index = assets.Tiles['*'].Particle
|
||||
particle.Tiles = assets.Tiles['*'].Tiles
|
||||
|
||||
pos.Update(
|
||||
position.X-(16), // FIXME: use global tilesize!
|
||||
|
||||
92
systems/collision_system.go
Normal file
92
systems/collision_system.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package systems
|
||||
|
||||
import (
|
||||
"openquell/components"
|
||||
. "openquell/components"
|
||||
"openquell/grid"
|
||||
"openquell/observers"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
"github.com/mlange-42/arche/generic"
|
||||
)
|
||||
|
||||
type CollisionSystem struct {
|
||||
World *ecs.World
|
||||
Selector *generic.Filter4[Position, Velocity, Renderable, Collider]
|
||||
GridContainer *grid.GridContainer
|
||||
}
|
||||
|
||||
func NewCollisionSystem(world *ecs.World, gridcontainer *grid.GridContainer) System {
|
||||
system := &CollisionSystem{
|
||||
Selector: generic.NewFilter4[Position, Velocity, Renderable, Collider](),
|
||||
GridContainer: gridcontainer,
|
||||
World: world,
|
||||
}
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
func (system CollisionSystem) Update() error {
|
||||
entityobserver := observers.GetEntityObserver(system.World)
|
||||
|
||||
posID := ecs.ComponentID[components.Position](system.World)
|
||||
EntitiesToRemove := []ecs.Entity{}
|
||||
query := system.Selector.Query(system.World)
|
||||
|
||||
for query.Next() {
|
||||
position, velocity, _, handler := query.Get()
|
||||
if velocity.Moving() {
|
||||
// check if we bump agains the screen edge
|
||||
ok, newpos := system.GridContainer.Grid.BumpEdge(position, velocity)
|
||||
if ok {
|
||||
if handler.EdgeHandler != nil {
|
||||
if !handler.EdgeHandler(system.World, position, newpos, velocity, query.Entity()) {
|
||||
EntitiesToRemove = append(EntitiesToRemove, query.Entity())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check if we collide with some solid entity
|
||||
ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(position, velocity, true)
|
||||
if ok {
|
||||
intersects, newpos := tilepos.Intersects(position, velocity)
|
||||
if intersects {
|
||||
if handler.CollisionHandler != nil {
|
||||
if !handler.CollisionHandler(system.World, position, newpos, velocity, query.Entity()) {
|
||||
EntitiesToRemove = append(EntitiesToRemove, query.Entity())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if current entity bumps into another entity
|
||||
for foreign_entity := range entityobserver.Entities {
|
||||
if foreign_entity == query.Entity() {
|
||||
// don't check entity against itself
|
||||
continue
|
||||
}
|
||||
|
||||
foreign_position := (*Position)(system.World.Get(foreign_entity, posID))
|
||||
|
||||
ok, newpos := foreign_position.Intersects(position, velocity)
|
||||
if ok {
|
||||
if handler.CollisionHandler != nil {
|
||||
if !handler.CollisionHandler(system.World, position, newpos, velocity, foreign_entity) {
|
||||
EntitiesToRemove = append(EntitiesToRemove, query.Entity())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for _, entity := range EntitiesToRemove {
|
||||
system.World.RemoveEntity(entity)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (system *CollisionSystem) Draw(screen *ebiten.Image) {}
|
||||
@@ -48,7 +48,7 @@ func NewDestroyableSystem(world *ecs.World, gridcontainer *grid.GridContainer) S
|
||||
}
|
||||
|
||||
func (system *DestroyableSystem) Update() error {
|
||||
observer := observers.GetGameObserver(system.World)
|
||||
playerobserver := observers.GetPlayerObserver(system.World)
|
||||
posID := ecs.ComponentID[components.Position](system.World)
|
||||
veloID := ecs.ComponentID[components.Velocity](system.World)
|
||||
|
||||
@@ -59,7 +59,7 @@ func (system *DestroyableSystem) Update() error {
|
||||
for query.Next() {
|
||||
doorposition, renderable, door := query.Get()
|
||||
|
||||
for _, player := range observer.GetPlayers() {
|
||||
for player := range playerobserver.Entities {
|
||||
if !system.World.Alive(player) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package systems
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/draw"
|
||||
"log/slog"
|
||||
. "openquell/components"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
@@ -37,33 +40,32 @@ func NewGridSystem(world *ecs.World, width, height,
|
||||
return system
|
||||
}
|
||||
|
||||
func (system *GridSystem) Update() error {
|
||||
return nil
|
||||
}
|
||||
func (system *GridSystem) Update() error { return nil }
|
||||
|
||||
func (system *GridSystem) Draw(screen *ebiten.Image) {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
query := system.Selector.Query(system.World)
|
||||
|
||||
if !system.UseCache || query.Count() != system.Count {
|
||||
slog.Debug("entity number changes", "old", system.Count, "current", query.Count())
|
||||
// map not cached or cacheable, write it to the cache
|
||||
system.Cache.DrawImage(system.Background, op)
|
||||
draw.Draw(system.Cache, system.Background.Bounds(), system.Background, image.ZP, draw.Src)
|
||||
|
||||
system.Count = query.Count()
|
||||
counter := 0
|
||||
|
||||
for query.Next() {
|
||||
sprite, pos, _ := query.Get()
|
||||
counter++
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
|
||||
system.Cache.DrawImage(sprite.Image, op)
|
||||
|
||||
//slog.Debug("drawing sprite", "sprite", sprite, "point", pos.Point())
|
||||
draw.Draw(
|
||||
system.Cache,
|
||||
image.Rect(pos.X, pos.Y, pos.X+pos.Cellsize, pos.Y+pos.Cellsize),
|
||||
sprite.Image, image.ZP, draw.Over)
|
||||
}
|
||||
|
||||
op.GeoM.Reset()
|
||||
screen.DrawImage(system.Cache, op)
|
||||
|
||||
system.UseCache = true
|
||||
|
||||
} else {
|
||||
// use the cached map
|
||||
op.GeoM.Reset()
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package systems
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"openquell/assets"
|
||||
"openquell/observers"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/mlange-42/arche/ecs"
|
||||
"github.com/solarlune/ldtkgo"
|
||||
)
|
||||
|
||||
const MaxLetters int = 52
|
||||
|
||||
type HudSystem struct {
|
||||
World *ecs.World
|
||||
Cellsize int
|
||||
Observer *observers.GameObserver
|
||||
Plan *ldtkgo.Level
|
||||
MenuIcon *ebiten.Image
|
||||
}
|
||||
|
||||
func NewHudSystem(world *ecs.World, plan *ldtkgo.Level) System {
|
||||
system := &HudSystem{
|
||||
Observer: observers.GetGameObserver(world),
|
||||
World: world,
|
||||
Plan: plan,
|
||||
MenuIcon: assets.Assets["menu"],
|
||||
}
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
func (system *HudSystem) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (system *HudSystem) Draw(screen *ebiten.Image) {
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
screen.DrawImage(assets.Assets["hud"], op)
|
||||
/*
|
||||
ebitenutil.DebugPrintAt(screen, fmt.Sprintf(
|
||||
"FPS: %02.f TPS: %02.f Level %s: %s",
|
||||
ebiten.ActualFPS(),
|
||||
ebiten.ActualTPS(),
|
||||
system.Plan.Name,
|
||||
system.Plan.Description,
|
||||
), 10, 10)
|
||||
*/
|
||||
|
||||
score := fmt.Sprintf("Score: %d", system.Observer.GetScore())
|
||||
level := fmt.Sprintf("Level %d: %s", system.Plan.PropertyByIdentifier("level").AsInt(),
|
||||
strings.ReplaceAll(system.Plan.Identifier, "_", " "))
|
||||
des := system.Plan.PropertyByIdentifier("description").AsString()
|
||||
|
||||
assets.FontRenderer.Renderer.SetSizePx(20)
|
||||
assets.FontRenderer.Renderer.SetTarget(screen)
|
||||
|
||||
system.Print(score, 450, 22)
|
||||
x := system.GetTextXCentered(des)
|
||||
|
||||
system.Print(des, x, 470)
|
||||
system.Print(level, 10, 22)
|
||||
|
||||
op.GeoM.Reset()
|
||||
op.GeoM.Translate(600, 0)
|
||||
screen.DrawImage(system.MenuIcon, op)
|
||||
}
|
||||
|
||||
func (system *HudSystem) Print(text string, x, y int) {
|
||||
fg := &color.RGBA{0x4c, 0, 0xff, 255}
|
||||
bg := &color.RGBA{0x9a, 0x6f, 0xff, 255}
|
||||
|
||||
assets.FontRenderer.Renderer.SetColor(bg)
|
||||
assets.FontRenderer.Renderer.Draw(text, x, y)
|
||||
assets.FontRenderer.Renderer.SetColor(fg)
|
||||
assets.FontRenderer.Renderer.Draw(text, x-1, y-1)
|
||||
}
|
||||
|
||||
func (system *HudSystem) GetTextXCentered(text string) int {
|
||||
size := len(text)
|
||||
lettersize := system.Observer.Width / MaxLetters
|
||||
descsize := lettersize * size
|
||||
return system.Observer.Width/2 - descsize/2
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package systems
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"openquell/assets"
|
||||
"openquell/components"
|
||||
. "openquell/components"
|
||||
. "openquell/config"
|
||||
@@ -30,106 +31,30 @@ func NewObstacleSystem(world *ecs.World, gridcontainer *grid.GridContainer) Syst
|
||||
return system
|
||||
}
|
||||
|
||||
func ObstacleBumpEdgeResponder(
|
||||
pos *components.Position,
|
||||
vel *components.Velocity,
|
||||
newpos *components.Position) {
|
||||
|
||||
pos.Set(newpos)
|
||||
}
|
||||
|
||||
func ObstacleBumpWallResponder(
|
||||
pos *components.Position,
|
||||
vel *components.Velocity,
|
||||
newpos *components.Position) {
|
||||
|
||||
pos.Set(newpos)
|
||||
vel.ResetDirectionAndStop()
|
||||
}
|
||||
|
||||
func (system *ObstacleSystem) Update() error {
|
||||
observer := observers.GetGameObserver(system.World)
|
||||
playerobserver := observers.GetPlayerObserver(system.World)
|
||||
gameobserver := observers.GetGameObserver(system.World)
|
||||
|
||||
if observer.Lost {
|
||||
if gameobserver.Lost {
|
||||
return nil
|
||||
}
|
||||
|
||||
posID := ecs.ComponentID[components.Position](system.World)
|
||||
veloID := ecs.ComponentID[components.Velocity](system.World)
|
||||
var gameover bool
|
||||
|
||||
EntitiesToRemove := []ecs.Entity{}
|
||||
|
||||
query := system.Selector.Query(system.World)
|
||||
|
||||
for query.Next() {
|
||||
obsposition, obsvelocity, _, _ := query.Get()
|
||||
|
||||
// check if one player has bumped into current obstacle
|
||||
for _, player := range observer.GetPlayers() {
|
||||
if !system.World.Alive(player) {
|
||||
continue
|
||||
}
|
||||
|
||||
playerposition := (*Position)(system.World.Get(player, posID))
|
||||
playervelocity := (*Velocity)(system.World.Get(player, veloID))
|
||||
|
||||
ok, newpos := obsposition.Intersects(playerposition, playervelocity)
|
||||
if ok {
|
||||
if CheckObstacleSide(playervelocity, obsvelocity) {
|
||||
// player died
|
||||
EntitiesToRemove = append(EntitiesToRemove, player)
|
||||
} else {
|
||||
// bumped into nonlethal obstacle side, stop the
|
||||
// player, set the obstacle in motion, if possible
|
||||
//slog.Debug("bump not die", "playervelo", playervelocity)
|
||||
obsvelocity.Set(playervelocity)
|
||||
//slog.Debug("bump not die", "obsvelo", obsvelocity)
|
||||
playervelocity.Change(Stop)
|
||||
playerposition.Set(newpos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if current obstacle bumped into another obstacle
|
||||
for _, foreign_obstacle := range observer.GetObstacles() {
|
||||
if foreign_obstacle == query.Entity() {
|
||||
// don't check obstacle against itself
|
||||
continue
|
||||
}
|
||||
|
||||
foreign_obstacle_position := (*Position)(system.World.Get(foreign_obstacle, posID))
|
||||
|
||||
ok, newpos := foreign_obstacle_position.Intersects(obsposition, obsvelocity)
|
||||
if ok {
|
||||
//slog.Debug("bumped into foreign obstacle", "obstacle", foreign_obstacle)
|
||||
obsposition.Set(newpos)
|
||||
obsvelocity.ResetDirectionAndStop()
|
||||
}
|
||||
}
|
||||
|
||||
// check if [moving] obstacle collides with walls or edges
|
||||
system.GridContainer.Grid.CheckGridCollision(
|
||||
obsposition, obsvelocity, ObstacleBumpEdgeResponder, ObstacleBumpWallResponder)
|
||||
|
||||
if obsvelocity.Moving() {
|
||||
obsposition.Move(obsvelocity)
|
||||
}
|
||||
if len(playerobserver.Entities) == 0 {
|
||||
// FIXME: check this in game or elsewhere
|
||||
gameover = true
|
||||
}
|
||||
|
||||
for _, entity := range EntitiesToRemove {
|
||||
slog.Debug("remove player")
|
||||
system.World.RemoveEntity(entity)
|
||||
}
|
||||
|
||||
if len(observer.GetPlayers()) == 0 {
|
||||
// lost
|
||||
timer := observer.StopTimer
|
||||
if gameover {
|
||||
// winner, winner, chicken dinner!
|
||||
timer := gameobserver.StopTimer
|
||||
|
||||
if !timer.Running {
|
||||
timer.Start(LEVEL_END_WAIT)
|
||||
}
|
||||
|
||||
observer.Gameover()
|
||||
gameobserver.Gameover()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -150,15 +75,39 @@ func (system *ObstacleSystem) Draw(screen *ebiten.Image) {
|
||||
}
|
||||
}
|
||||
|
||||
func (system *ObstacleSystem) AddParticle(position *components.Position) {
|
||||
particleobserver := observers.GetParticleObserver(system.World)
|
||||
|
||||
ptmapper := generic.NewMap3[
|
||||
components.Position,
|
||||
components.Particle,
|
||||
components.Timer,
|
||||
](system.World)
|
||||
|
||||
entity := ptmapper.New()
|
||||
pos, particle, timer := ptmapper.Get(entity)
|
||||
particleobserver.AddEntity(entity)
|
||||
|
||||
particle.Index = assets.Tiles['*'].Particle
|
||||
particle.Tiles = assets.Tiles['*'].Tiles
|
||||
|
||||
pos.Update(
|
||||
position.X-(16), // FIXME: use global tilesize!
|
||||
position.Y-(16),
|
||||
64,
|
||||
)
|
||||
|
||||
timer.Start(PARTICLE_LOOPWAIT)
|
||||
}
|
||||
|
||||
// return true if obstacle weapon points into the direction the player is moving
|
||||
// OR if the weapon points towards a non-moving player
|
||||
func CheckObstacleSide(playervelocity *Velocity, obsvelocity *Velocity) bool {
|
||||
func CheckObstacleSide(playervelocity *Velocity, obsdirection int) bool {
|
||||
movingdirection := playervelocity.InvertDirection()
|
||||
|
||||
if movingdirection == Stop || movingdirection == obsvelocity.PointingAt {
|
||||
if movingdirection == Stop || movingdirection == obsdirection {
|
||||
slog.Debug("Damaging obstacle collision",
|
||||
"playerdirection", util.DirectionStr(playervelocity.Direction),
|
||||
"obsdirection", util.DirectionStr(obsvelocity.PointingAt),
|
||||
"obsdirection", util.DirectionStr(obsdirection),
|
||||
"movingdirection", util.DirectionStr(movingdirection),
|
||||
)
|
||||
return true
|
||||
|
||||
@@ -2,11 +2,9 @@ package systems
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"openquell/components"
|
||||
. "openquell/components"
|
||||
. "openquell/config"
|
||||
"openquell/grid"
|
||||
"openquell/observers"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
@@ -18,91 +16,95 @@ type PlayerSystem struct {
|
||||
World *ecs.World
|
||||
Selector *generic.Filter4[Position, Velocity, Player, Renderable]
|
||||
GridContainer *grid.GridContainer
|
||||
Width, Height int
|
||||
}
|
||||
|
||||
func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer, width, height int) System {
|
||||
func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer) System {
|
||||
system := &PlayerSystem{
|
||||
Selector: generic.NewFilter4[Position, Velocity, Player, Renderable](),
|
||||
GridContainer: gridcontainer,
|
||||
World: world,
|
||||
Width: width,
|
||||
Height: height,
|
||||
}
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
func PlayerBumpEdgeResponder(
|
||||
pos *components.Position,
|
||||
vel *components.Velocity,
|
||||
newpos *components.Position) {
|
||||
|
||||
pos.Set(newpos)
|
||||
}
|
||||
|
||||
func PlayerBumpWallResponder(
|
||||
pos *components.Position,
|
||||
vel *components.Velocity,
|
||||
newpos *components.Position) {
|
||||
|
||||
pos.Set(newpos)
|
||||
vel.Change(Stop)
|
||||
}
|
||||
|
||||
func (system PlayerSystem) Update() error {
|
||||
var EntitiesToRemove []ecs.Entity
|
||||
// first check if we need to switch player
|
||||
switchable := false
|
||||
query := system.Selector.Query(system.World)
|
||||
for query.Next() {
|
||||
_, _, player, _ := query.Get()
|
||||
if !player.IsPrimary {
|
||||
switchable = true
|
||||
}
|
||||
}
|
||||
|
||||
// check if we need to switch player[s]
|
||||
system.SwitchPlayers()
|
||||
if switchable {
|
||||
query := system.Selector.Query(system.World)
|
||||
for query.Next() {
|
||||
_, _, player, render := query.Get()
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyTab) {
|
||||
slog.Debug("switch players")
|
||||
if player.IsPrimary {
|
||||
player.IsPrimary = false
|
||||
render.Image = player.SwitchSprite()
|
||||
} else {
|
||||
player.IsPrimary = true
|
||||
render.Image = player.SwitchSprite()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check player movements etc
|
||||
query := system.Selector.Query(system.World)
|
||||
count := query.Count()
|
||||
|
||||
query = system.Selector.Query(system.World)
|
||||
for query.Next() {
|
||||
playerposition, velocity, player, _ := query.Get()
|
||||
_, velocity, player, _ := query.Get()
|
||||
|
||||
if !player.IsPrimary {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if the user alters or initiates movement
|
||||
system.CheckMovement(playerposition, velocity, player)
|
||||
|
||||
// check if player collides with walls or edges
|
||||
system.GridContainer.Grid.CheckGridCollision(
|
||||
playerposition, velocity, PlayerBumpEdgeResponder, PlayerBumpWallResponder)
|
||||
|
||||
if count > 1 {
|
||||
// check if player collides with another player, fuse them if any
|
||||
EntitiesToRemove = system.CheckPlayerCollision(playerposition, velocity, query.Entity())
|
||||
} else {
|
||||
// only 1 player left or one is max
|
||||
EntitiesToRemove = system.CheckPlayerLooping(
|
||||
playerposition, velocity, player, query.Entity())
|
||||
}
|
||||
|
||||
if !velocity.Moving() {
|
||||
// disable loop detection
|
||||
player.LoopCount = 0
|
||||
switch {
|
||||
case ebiten.IsKeyPressed(ebiten.KeyRight):
|
||||
velocity.Change(East)
|
||||
case ebiten.IsKeyPressed(ebiten.KeyLeft):
|
||||
velocity.Change(West)
|
||||
case ebiten.IsKeyPressed(ebiten.KeyDown):
|
||||
velocity.Change(South)
|
||||
case ebiten.IsKeyPressed(ebiten.KeyUp):
|
||||
velocity.Change(North)
|
||||
case ebiten.IsKeyPressed(ebiten.Key1):
|
||||
velocity.Speed = 1
|
||||
case ebiten.IsKeyPressed(ebiten.Key2):
|
||||
velocity.Speed = 2
|
||||
case ebiten.IsKeyPressed(ebiten.Key3):
|
||||
velocity.Speed = 3
|
||||
case ebiten.IsKeyPressed(ebiten.Key4):
|
||||
velocity.Speed = 4
|
||||
case ebiten.IsKeyPressed(ebiten.Key5):
|
||||
velocity.Speed = 5
|
||||
case ebiten.IsKeyPressed(ebiten.Key6):
|
||||
velocity.Speed = 6
|
||||
case ebiten.IsKeyPressed(ebiten.Key7):
|
||||
velocity.Speed = 7
|
||||
case ebiten.IsKeyPressed(ebiten.Key8):
|
||||
velocity.Speed = 8
|
||||
case ebiten.IsKeyPressed(ebiten.Key9):
|
||||
velocity.Speed = 9
|
||||
// other keys: <tab>: switch player, etc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2nd pass: move player after obstacle or collectible updates
|
||||
query = system.Selector.Query(system.World)
|
||||
for query.Next() {
|
||||
// move player after obstacle or collectible updates
|
||||
playerposition, velocity, _, _ := query.Get()
|
||||
playerposition.Move(velocity)
|
||||
}
|
||||
|
||||
// we may have lost players, remove them here
|
||||
|
||||
for _, entity := range EntitiesToRemove {
|
||||
slog.Debug("remove player", "ent", entity.IsZero())
|
||||
system.World.RemoveEntity(entity)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -120,142 +122,3 @@ func (system *PlayerSystem) Draw(screen *ebiten.Image) {
|
||||
screen.DrawImage(sprite.Image, op)
|
||||
}
|
||||
}
|
||||
|
||||
func (system *PlayerSystem) SwitchPlayers() {
|
||||
// first check if we need to switch player
|
||||
switchable := false
|
||||
query := system.Selector.Query(system.World)
|
||||
count := query.Count()
|
||||
|
||||
for query.Next() {
|
||||
_, _, player, _ := query.Get()
|
||||
if !player.IsPrimary {
|
||||
switchable = true
|
||||
}
|
||||
}
|
||||
|
||||
if switchable {
|
||||
query := system.Selector.Query(system.World)
|
||||
for query.Next() {
|
||||
_, _, player, render := query.Get()
|
||||
|
||||
if count == 1 && !player.IsPrimary {
|
||||
// there's only one player left, make it the primary one
|
||||
player.IsPrimary = true
|
||||
render.Image = player.SwitchSprite()
|
||||
} else {
|
||||
// many players, switch when requested
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyTab) {
|
||||
slog.Debug("switch players")
|
||||
if player.IsPrimary {
|
||||
player.IsPrimary = false
|
||||
render.Image = player.SwitchSprite()
|
||||
} else {
|
||||
player.IsPrimary = true
|
||||
render.Image = player.SwitchSprite()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (system *PlayerSystem) CheckMovement(
|
||||
position *components.Position,
|
||||
velocity *components.Velocity,
|
||||
player *components.Player) {
|
||||
|
||||
moved := false
|
||||
observer := observers.GetGameObserver(system.World)
|
||||
|
||||
if !velocity.Moving() {
|
||||
switch {
|
||||
case inpututil.IsKeyJustPressed(ebiten.KeyRight):
|
||||
velocity.Change(East)
|
||||
moved = true
|
||||
case inpututil.IsKeyJustPressed(ebiten.KeyLeft):
|
||||
velocity.Change(West)
|
||||
moved = true
|
||||
case inpututil.IsKeyJustPressed(ebiten.KeyDown):
|
||||
velocity.Change(South)
|
||||
moved = true
|
||||
case inpututil.IsKeyJustPressed(ebiten.KeyUp):
|
||||
velocity.Change(North)
|
||||
moved = true
|
||||
}
|
||||
|
||||
if moved {
|
||||
// will be reset every time the user initiates player movement
|
||||
player.LoopPos.Set(position)
|
||||
player.LoopCount = 0
|
||||
observer.AddMove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (system *PlayerSystem) CheckPlayerCollision(
|
||||
position *components.Position,
|
||||
velocity *components.Velocity,
|
||||
player ecs.Entity) []ecs.Entity {
|
||||
|
||||
observer := observers.GetGameObserver(system.World)
|
||||
posID := ecs.ComponentID[components.Position](system.World)
|
||||
EntitiesToRemove := []ecs.Entity{}
|
||||
|
||||
for _, otherplayer := range observer.GetPlayers() {
|
||||
if !system.World.Alive(otherplayer) {
|
||||
continue
|
||||
}
|
||||
|
||||
if otherplayer == player {
|
||||
continue
|
||||
}
|
||||
|
||||
otherposition := (*Position)(system.World.Get(otherplayer, posID))
|
||||
|
||||
ok, _ := otherposition.Intersects(position, velocity)
|
||||
if ok {
|
||||
// keep displaying it until the two fully intersect
|
||||
EntitiesToRemove = append(EntitiesToRemove, otherplayer)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: add an animation highlighting the fusion
|
||||
return EntitiesToRemove
|
||||
}
|
||||
|
||||
func (system *PlayerSystem) CheckPlayerLooping(
|
||||
position *components.Position,
|
||||
velocity *components.Velocity,
|
||||
player *components.Player,
|
||||
entity ecs.Entity) []ecs.Entity {
|
||||
|
||||
if player.LoopPos.Rect == nil {
|
||||
// hasn't moved
|
||||
return nil
|
||||
}
|
||||
EntitiesToRemove := []ecs.Entity{}
|
||||
|
||||
ok, _ := player.LoopPos.Intersects(position, velocity)
|
||||
if ok {
|
||||
// Fatal: loop detected with last player
|
||||
player.LoopStart = true
|
||||
} else {
|
||||
// no intersection with old pos anymore
|
||||
if player.LoopStart {
|
||||
// loop detection active
|
||||
player.LoopCount++
|
||||
max := system.Width
|
||||
if velocity.Direction == North || velocity.Direction == South {
|
||||
max = system.Height
|
||||
}
|
||||
|
||||
// we haved moved one time around the whole screen, loose
|
||||
if (player.LoopCount * velocity.Speed) > max {
|
||||
EntitiesToRemove = append(EntitiesToRemove, entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EntitiesToRemove
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func NewTransientSystem(world *ecs.World, gridcontainer *grid.GridContainer) Sys
|
||||
}
|
||||
|
||||
func (system *TransientSystem) Update() error {
|
||||
observer := observers.GetGameObserver(system.World)
|
||||
playerobserver := observers.GetPlayerObserver(system.World)
|
||||
posID := ecs.ComponentID[components.Position](system.World)
|
||||
veloID := ecs.ComponentID[components.Velocity](system.World)
|
||||
|
||||
@@ -59,7 +59,7 @@ func (system *TransientSystem) Update() error {
|
||||
for query.Next() {
|
||||
transientposition, _, transient := query.Get()
|
||||
|
||||
for _, player := range observer.GetPlayers() {
|
||||
for player := range playerobserver.Entities {
|
||||
if !system.World.Alive(player) {
|
||||
continue
|
||||
}
|
||||
@@ -79,19 +79,17 @@ func (system *TransientSystem) Update() error {
|
||||
Position: *transientposition,
|
||||
NewSprite: transient.GetNext(),
|
||||
})
|
||||
slog.Debug("transient added to make solid")
|
||||
slog.Debug("done transient", "transient", transientposition)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, convertible := range EntitiestoMakeSolid {
|
||||
slog.Debug("transient remove")
|
||||
// remove transient entity
|
||||
system.World.RemoveEntity(convertible.Entity)
|
||||
|
||||
// replace with solid entity
|
||||
slog.Debug("transient add solid")
|
||||
entity := system.SolidMapper.New()
|
||||
pos, render, _, _ := system.SolidMapper.Get(entity)
|
||||
|
||||
|
||||
561
wasm_exec.js
561
wasm_exec.js
@@ -1,561 +0,0 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!globalThis.fs) {
|
||||
let outputBuf = "";
|
||||
globalThis.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substring(0, nl));
|
||||
outputBuf = outputBuf.substring(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
callback(enosys());
|
||||
return;
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
chmod(path, mode, callback) { callback(enosys()); },
|
||||
chown(path, uid, gid, callback) { callback(enosys()); },
|
||||
close(fd, callback) { callback(enosys()); },
|
||||
fchmod(fd, mode, callback) { callback(enosys()); },
|
||||
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
||||
fstat(fd, callback) { callback(enosys()); },
|
||||
fsync(fd, callback) { callback(null); },
|
||||
ftruncate(fd, length, callback) { callback(enosys()); },
|
||||
lchown(path, uid, gid, callback) { callback(enosys()); },
|
||||
link(path, link, callback) { callback(enosys()); },
|
||||
lstat(path, callback) { callback(enosys()); },
|
||||
mkdir(path, perm, callback) { callback(enosys()); },
|
||||
open(path, flags, mode, callback) { callback(enosys()); },
|
||||
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
||||
readdir(path, callback) { callback(enosys()); },
|
||||
readlink(path, callback) { callback(enosys()); },
|
||||
rename(from, to, callback) { callback(enosys()); },
|
||||
rmdir(path, callback) { callback(enosys()); },
|
||||
stat(path, callback) { callback(enosys()); },
|
||||
symlink(path, link, callback) { callback(enosys()); },
|
||||
truncate(path, length, callback) { callback(enosys()); },
|
||||
unlink(path, callback) { callback(enosys()); },
|
||||
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
||||
};
|
||||
}
|
||||
|
||||
if (!globalThis.process) {
|
||||
globalThis.process = {
|
||||
getuid() { return -1; },
|
||||
getgid() { return -1; },
|
||||
geteuid() { return -1; },
|
||||
getegid() { return -1; },
|
||||
getgroups() { throw enosys(); },
|
||||
pid: -1,
|
||||
ppid: -1,
|
||||
umask() { throw enosys(); },
|
||||
cwd() { throw enosys(); },
|
||||
chdir() { throw enosys(); },
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalThis.crypto) {
|
||||
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
||||
}
|
||||
|
||||
if (!globalThis.performance) {
|
||||
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
||||
}
|
||||
|
||||
if (!globalThis.TextEncoder) {
|
||||
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
if (!globalThis.TextDecoder) {
|
||||
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
globalThis.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this.exit = (code) => {
|
||||
if (code !== 0) {
|
||||
console.warn("exit code:", code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise((resolve) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const setInt32 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = this.mem.getUint32(addr + 0, true);
|
||||
const high = this.mem.getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = this.mem.getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = this.mem.getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number" && v !== 0) {
|
||||
if (isNaN(v)) {
|
||||
this.mem.setUint32(addr + 4, nanHead, true);
|
||||
this.mem.setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
this.mem.setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
this.mem.setFloat64(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = this._values.length;
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "object":
|
||||
if (v !== null) {
|
||||
typeFlag = 1;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4;
|
||||
break;
|
||||
}
|
||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
this.mem.setUint32(addr, id, true);
|
||||
}
|
||||
|
||||
const loadSlice = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (addr) => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
_gotest: {
|
||||
add: (a, b) => a + b,
|
||||
},
|
||||
gojs: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
"runtime.wasmExit": (sp) => {
|
||||
sp >>>= 0;
|
||||
const code = this.mem.getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._goRefCounts;
|
||||
delete this._ids;
|
||||
delete this._idPool;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
"runtime.wasmWrite": (sp) => {
|
||||
sp >>>= 0;
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = this.mem.getInt32(sp + 24, true);
|
||||
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||
},
|
||||
|
||||
// func resetMemoryDataView()
|
||||
"runtime.resetMemoryDataView": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
},
|
||||
|
||||
// func nanotime1() int64
|
||||
"runtime.nanotime1": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
"runtime.walltime": (sp) => {
|
||||
sp >>>= 0;
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(id, setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8),
|
||||
));
|
||||
this.mem.setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
"runtime.clearTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
"runtime.getRandomData": (sp) => {
|
||||
sp >>>= 0;
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getUint32(sp + 8, true);
|
||||
this._goRefCounts[id]--;
|
||||
if (this._goRefCounts[id] === 0) {
|
||||
const v = this._values[id];
|
||||
this._values[id] = null;
|
||||
this._ids.delete(v);
|
||||
this._idPool.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (sp) => {
|
||||
sp >>>= 0;
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
"syscall/js.copyBytesToJS": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
"debug": (value) => {
|
||||
console.log(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
if (!(instance instanceof WebAssembly.Instance)) {
|
||||
throw new Error("Go.run: WebAssembly.Instance expected");
|
||||
}
|
||||
this._inst = instance;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
globalThis,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
this._ids = new Map([ // mapping from JS values to reference ids
|
||||
[0, 1],
|
||||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[globalThis, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = (str) => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + "\0");
|
||||
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach((arg) => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
keys.forEach((key) => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach((ptr) => {
|
||||
this.mem.setUint32(offset, ptr, true);
|
||||
this.mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error("total length of command line and environment variables exceeds limit");
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user