Compare commits

..

16 Commits

Author SHA1 Message Date
55c2232e08 Fix a SIGSEGV 2024-10-23 23:54:48 +01:00
ac4675fa2c Fix some log.Fatal calls 2024-10-23 23:47:55 +01:00
5f7654d267 Update dependencies 2024-10-23 23:45:26 +01:00
89888ce004 Stringify AnimAction 2024-10-23 23:44:44 +01:00
16767da6f1 Bump a few more dependencies 2023-07-16 23:11:01 +01:00
c5b80ed8bc Bump ebiten 2023-07-16 22:51:16 +01:00
891edecc60 Update dependencies 2023-01-17 22:10:46 +00:00
85979834c8 Update ebiten and golang 2021-10-23 22:44:39 +01:00
96dbb297cd Bump ebiten 2021-03-01 22:26:45 +00:00
92fa0fc5d6 UNTESTED: ebiten v2 2020-11-21 19:27:09 +00:00
c5e6abb798 First attempt at character orientation 2020-06-13 23:10:21 +01:00
5df050b4ef Substitute unknown glyphs 2020-06-13 18:23:50 +01:00
4d336b9189 Get character stats (kind of) displaying in-scenario 2020-06-13 18:11:45 +01:00
3b7cfb6ecc Drag flow into view-map
This is pretty awful, but will let me wire up items more easily without
needing to do the big refactor into independent menu handlers
2020-06-13 16:37:39 +01:00
7677c30572 Start displaying characters on maps 2020-06-13 15:07:32 +01:00
eac6017c2c Count sprite draw calls 2020-06-13 13:42:26 +01:00
37 changed files with 603 additions and 323 deletions

View File

@@ -184,3 +184,10 @@ $ ./scripts/convert-wav ./orig/Wav
As with video playback, the ambition is to *eventually* remove this dependency As with video playback, the ambition is to *eventually* remove this dependency
and operate on the unmodified files instead. and operate on the unmodified files instead.
## Resources
Here's a collection of links that I'm finding useful or otherwise interesting,
and don't want to lose track of...
* [Historical geocities modders](http://www.oocities.org/timessquare/galaxy/6777/)

View File

@@ -221,5 +221,8 @@ func loadIdx(idxPath string) {
for i, group := range idx.Groups { for i, group := range idx.Groups {
log.Printf("Group %2d: %4d records, start sprite is %6d", i, len(group.Records), group.Spec.SpriteIdx) log.Printf("Group %2d: %4d records, start sprite is %6d", i, len(group.Records), group.Spec.SpriteIdx)
for i, rec := range group.Records {
log.Printf("\t%3d: %#+v", i, rec)
}
} }
} }

View File

@@ -7,7 +7,7 @@ import (
"math" "math"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"golang.org/x/image/colornames" "golang.org/x/image/colornames"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
@@ -100,7 +100,7 @@ func main() {
func (e *env) Update(screenX, screenY int) error { func (e *env) Update(screenX, screenY int) error {
if e.step == 0 || e.lastState != e.state { if e.step == 0 || e.lastState != e.state {
ani, err := e.assets.Animation(e.state.groupIdx, e.state.recIdx) ani, err := e.assets.Animation(e.state.groupIdx, e.state.recIdx, 0) // FIXME: why 0?
if err != nil { if err != nil {
return err return err
} }
@@ -131,7 +131,7 @@ func (e *env) Draw(screen *ebiten.Image) error {
if len(e.ani.Frames) > 0 { if len(e.ani.Frames) > 0 {
sprite := e.ani.Frames[e.step/4%len(e.ani.Frames)] sprite := e.ani.Frames[e.step/4%len(e.ani.Frames)]
return screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam}) screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam})
} }
return nil return nil

View File

@@ -7,7 +7,7 @@ import (
"math" "math"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
@@ -69,7 +69,7 @@ func main() {
win, err := ui.NewWindow(env, "View Font: "+*fontName, *winX, *winY) win, err := ui.NewWindow(env, "View Font: "+*fontName, *winX, *winY)
if err != nil { if err != nil {
log.Fatal("Couldn't create window: %v", err) log.Fatalf("Couldn't create window: %v", err)
} }
win.OnMouseWheel(env.changeZoom) win.OnMouseWheel(env.changeZoom)
@@ -103,9 +103,7 @@ func (e *env) Draw(screen *ebiten.Image) error {
op.GeoM.Translate(float64(xOff), 0) op.GeoM.Translate(float64(xOff), 0)
op.GeoM.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor op.GeoM.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor
if err := screen.DrawImage(glyph.Image, op); err != nil { screen.DrawImage(glyph.Image, op)
return err
}
xOff += glyph.Rect.Dx() xOff += glyph.Rect.Dx()
} }

View File

@@ -6,11 +6,13 @@ import (
"math" "math"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
"code.ur.gs/lupine/ordoor/internal/flow"
"code.ur.gs/lupine/ordoor/internal/scenario" "code.ur.gs/lupine/ordoor/internal/scenario"
"code.ur.gs/lupine/ordoor/internal/ship"
"code.ur.gs/lupine/ordoor/internal/ui" "code.ur.gs/lupine/ordoor/internal/ui"
) )
@@ -25,6 +27,7 @@ var (
) )
type env struct { type env struct {
flow *flow.Flow
scenario *scenario.Scenario scenario *scenario.Scenario
} }
@@ -51,27 +54,39 @@ func main() {
log.Fatalf("Failed to load scenario %v: %v", *gameMap, err) log.Fatalf("Failed to load scenario %v: %v", *gameMap, err)
} }
env := &env{ var realEnv *env
scenario: scenario, if cfg.DefaultEngineName == "ordoor" {
ship := &ship.Ship{}
flow, err := flow.New(assets, cfg, ship)
if err != nil {
log.Fatalf("Failed to setup flow: %v", err)
}
flow.SetScenario(scenario)
realEnv = &env{flow: flow, scenario: scenario}
} else {
realEnv = &env{scenario: scenario}
} }
win, err := ui.NewWindow(env, "View Map "+*gameMap, *winX, *winY) win, err := ui.NewWindow(realEnv, "View Map "+*gameMap, *winX, *winY)
if err != nil { if err != nil {
log.Fatal("Couldn't create window: %v", err) log.Fatalf("Couldn't create window: %v", err)
} }
step := 32
win.WhileKeyDown(ebiten.KeyLeft, env.changeOrigin(-step, +0))
win.WhileKeyDown(ebiten.KeyRight, env.changeOrigin(+step, +0))
win.WhileKeyDown(ebiten.KeyUp, env.changeOrigin(+0, -step))
win.WhileKeyDown(ebiten.KeyDown, env.changeOrigin(+0, +step))
for i := 0; i <= 6; i++ { for i := 0; i <= 6; i++ {
win.OnKeyUp(ebiten.Key1+ebiten.Key(i), env.setZIdx(i)) win.OnKeyUp(ebiten.Key1+ebiten.Key(i), realEnv.setZIdx(i))
} }
win.OnMouseClick(env.showCellData) win.OnMouseClick(realEnv.showCellData)
win.OnMouseWheel(env.changeZoom) win.OnMouseWheel(realEnv.changeZoom)
if realEnv.flow == nil {
step := 32
win.WhileKeyDown(ebiten.KeyLeft, realEnv.changeOrigin(-step, +0))
win.WhileKeyDown(ebiten.KeyRight, realEnv.changeOrigin(+step, +0))
win.WhileKeyDown(ebiten.KeyUp, realEnv.changeOrigin(+0, -step))
win.WhileKeyDown(ebiten.KeyDown, realEnv.changeOrigin(+0, +step))
}
if err := win.Run(); err != nil { if err := win.Run(); err != nil {
log.Fatal(err) log.Fatal(err)
@@ -79,11 +94,19 @@ func main() {
} }
func (e *env) Update(screenX, screenY int) error { func (e *env) Update(screenX, screenY int) error {
return e.scenario.Update(screenX, screenY) if e.flow != nil {
return e.flow.Update(screenX, screenY)
} else {
return e.scenario.Update(screenX, screenY)
}
} }
func (e *env) Draw(screen *ebiten.Image) error { func (e *env) Draw(screen *ebiten.Image) error {
return e.scenario.Draw(screen) if e.flow != nil {
return e.flow.Draw(screen)
} else {
return e.scenario.Draw(screen)
}
} }
func (e *env) changeOrigin(byX, byY int) func() { func (e *env) changeOrigin(byX, byY int) func() {

View File

@@ -5,7 +5,7 @@ import (
"log" "log"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
@@ -58,7 +58,7 @@ func main() {
win, err := ui.NewWindow(driver, "View Menu: "+*menuName, *winX, *winY) win, err := ui.NewWindow(driver, "View Menu: "+*menuName, *winX, *winY)
if err != nil { if err != nil {
log.Fatal("Couldn't create window: %v", err) log.Fatalf("Couldn't create window: %v", err)
} }
// Change the active dialogue // Change the active dialogue

View File

@@ -10,7 +10,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/maps" "code.ur.gs/lupine/ordoor/internal/maps"
"code.ur.gs/lupine/ordoor/internal/sets" "code.ur.gs/lupine/ordoor/internal/sets"
@@ -76,7 +76,7 @@ func main() {
win, err := ui.NewWindow(env, "View Map "+*mapFile, *winX, *winY) win, err := ui.NewWindow(env, "View Map "+*mapFile, *winX, *winY)
if err != nil { if err != nil {
log.Fatal("Couldn't create window: %v", err) log.Fatalf("Couldn't create window: %v", err)
} }
win.OnKeyUp(ebiten.KeyEnter, env.toggleAutoUpdate) win.OnKeyUp(ebiten.KeyEnter, env.toggleAutoUpdate)
@@ -173,11 +173,7 @@ func (e *env) Update(screenX, screenY int) error {
func (e *env) Draw(screen *ebiten.Image) error { func (e *env) Draw(screen *ebiten.Image) error {
gameMap := e.gameMap gameMap := e.gameMap
rect := gameMap.Rect() rect := gameMap.Rect()
imd, err := ebiten.NewImage(rect.Dx(), rect.Dy(), ebiten.FilterDefault) imd := ebiten.NewImage(rect.Dx(), rect.Dy())
if err != nil {
return err
}
for y := int(rect.Min.Y); y < int(rect.Max.Y); y++ { for y := int(rect.Min.Y); y < int(rect.Max.Y); y++ {
for x := int(rect.Min.X); x < int(rect.Max.X); x++ { for x := int(rect.Min.X); x < int(rect.Max.X); x++ {
@@ -193,7 +189,9 @@ func (e *env) Draw(screen *ebiten.Image) error {
cam.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor cam.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor
cam.Rotate(0.785) // Apply isometric angle cam.Rotate(0.785) // Apply isometric angle
return screen.DrawImage(imd, &ebiten.DrawImageOptions{GeoM: cam}) screen.DrawImage(imd, &ebiten.DrawImageOptions{GeoM: cam})
return nil
} }
// Converts pixel coordinates to cell coordinates // Converts pixel coordinates to cell coordinates

View File

@@ -7,7 +7,7 @@ import (
"math" "math"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
@@ -136,7 +136,9 @@ func (e *env) Draw(screen *ebiten.Image) error {
cam.Translate(float64(e.state.origin.X), float64(e.state.origin.Y)) // Move to origin cam.Translate(float64(e.state.origin.X), float64(e.state.origin.Y)) // Move to origin
cam.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor cam.Scale(e.state.zoom, e.state.zoom) // apply current zoom factor
return screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam}) screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam})
return nil
} }
func (e *env) changeSprite(by int) func() { func (e *env) changeSprite(by int) func() {

View File

@@ -7,7 +7,7 @@ import (
"math" "math"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
@@ -71,7 +71,7 @@ func main() {
win, err := ui.NewWindow(env, "View Set: "+*setName, *winX, *winY) win, err := ui.NewWindow(env, "View Set: "+*setName, *winX, *winY)
if err != nil { if err != nil {
log.Fatal("Couldn't create window: %v", err) log.Fatalf("Couldn't create window: %v", err)
} }
win.OnKeyUp(ebiten.KeyLeft, env.changeObjIdx(-1)) win.OnKeyUp(ebiten.KeyLeft, env.changeObjIdx(-1))
@@ -123,7 +123,9 @@ func (e *env) Draw(screen *ebiten.Image) error {
// TODO: centre the image // TODO: centre the image
return screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam}) screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam})
return nil
} }
func (e *env) changeObjIdx(by int) func() { func (e *env) changeObjIdx(by int) func() {

View File

@@ -610,11 +610,6 @@ ignoring it for now.
thingies, and it, padding? Minus a bit? 0x50 is another non-null byte. Then thingies, and it, padding? Minus a bit? 0x50 is another non-null byte. Then
it's all zeroes until one byte before the first name at 0xee. it's all zeroes until one byte before the first name at 0xee.
It's hard to say where the alignment should be at this point. We need to compare
more characters with each other. Other notes...
Characters are organised into Squads somehow.
Individual cells seem to have a flag to say "We have a character in us", but not Individual cells seem to have a flag to say "We have a character in us", but not
the number for the character themselves, so the coordinates must be in the the number for the character themselves, so the coordinates must be in the
per-character records also. There are several candidates for this. per-character records also. There are several candidates for this.
@@ -631,26 +626,50 @@ suggested above:
| Offset | Size | Meaning | | Offset | Size | Meaning |
| ------ | ---- | ------- | | ------ | ---- | ------- |
| 0 | 179 | ??? | | 0 | 178 | ??? |
| 178 | 1 | Character type |
| 179 | 80 | Character name | | 179 | 80 | Character name |
| 259 | 10 | Character attributes | | 259 | 1 | Weapon Skill |
| 260 | 1 | Ballistic Skill |
| 261 | 1 | Unknown |
| 262 | 1 | Leadership |
| 263 | 1 | Toughness |
| 264 | 1 | Strength |
| 265 | 1 | Action Points |
| 266 | 1 | Unknown |
| 267 | 1 | Unknown |
| 268 | 1 | Health |
| 269 | 495 | ??? | | 269 | 495 | ??? |
| 764 | 1(?) | Squad number? | | 764 | 1(?) | Squad number |
| 765 | 927 | ??? | | 765 | 895 | ??? |
| 1660 | 1? | Orientation? Could also be `0x680`... |
| 1661 | 31 | ??? |
There's still a lot of bytes to dig through, but this allows me to load the There's still a lot of bytes to dig through, but this allows me to load the
character names from Chapter01 correctly, with the exception of record 57 which character names from Chapter01 correctly, with the exception of record 57 which
just contains `\x02` and is then null-terminated all the way through. Looking just contains `\x02` and is then null-terminated all the way through - but maybe
at the bytes for one character record, I can easily correlate certain bytes to that's just a data thing.
various attributes; that's just done in code for the moment.
Given two characters of the same time, just in different locations, differing How about their types? `HasAction.dat` lists numbers for character types, and
those show up immediately before the name. Going from the character type to the
animation group is not yet fully deciphered - squad leaders mess up a direct
correlation - but a fixed offset table allows me to draw the characters \o/.
Putting 8 characters onto a map and orienting them in the compass points, we see
numbers ranging from 0 to 7 at 0x67c and 0x680. Assuming this is the scheme
used, north is value 1, northeast value 2, and northwest value 0.
Given two characters of the same type, just in different locations, differing
values are seen at: values are seen at:
* `0x103 - 0x10c` (hodgepodge) * `0x103 - 0x10c` (hodgepodge)
* `0x178 - 0x1be` (hodgepodge) * `0x178 - 0x1be` (hodgepodge)
* `0x2fc` (0, 1) - squad number? * `0x2fc` (0, 1) - squad number?
I can easily correlate certain bytes in the first range to various character
attributes. A few remain unset.
In Chapter01, picking a random character (Gorgon) and looking at his squadmates, In Chapter01, picking a random character (Gorgon) and looking at his squadmates,
they are all in the same squad, and no other characters are in that squad, so it they are all in the same squad, and no other characters are in that squad, so it
looks pretty diagnostic to me. There's nothing in the UI to indicate the squad, looks pretty diagnostic to me. There's nothing in the UI to indicate the squad,
@@ -658,10 +677,7 @@ though.
Now let's look for position. In my 2-character map, they're at 65,50 and 70,55. Now let's look for position. In my 2-character map, they're at 65,50 and 70,55.
Within a character, I see those numbers repeated twice - around `0x1b{9,a}` and Within a character, I see those numbers repeated twice - around `0x1b{9,a}` and
`0x1b{d,e}`. `0x1b{d,e}`. This may be some kind of multiple-squares-taken-up thing.
Characters don't seem to take up multiple x,y squares, but they *can* take up
multiple Z levels. So maybe this is a bounding box of some kind?
Adding a (tall) Lord of Change to the map gave me `02 36 45 00 02 37 45`, which Adding a (tall) Lord of Change to the map gave me `02 36 45 00 02 37 45`, which
doesn't quite match what my eyes are telling me for Z,Y,X. In addition, the data doesn't quite match what my eyes are telling me for Z,Y,X. In addition, the data
@@ -673,21 +689,6 @@ Down in `0x679` (Chaos Sorcerer) or `0x68D` (Lord of Change), the map coords for
the *other* character appears, which is downright odd. For now, just use the the *other* character appears, which is downright odd. For now, just use the
first-indexed value. first-indexed value.
How about their types? We need to be able to work out which animations to show,
so they must be uniquely identified somehow. Ultramarine captain is animation
group 12, while chaos lord is group... 14? Not certain. I don't see either of
those numbers in a promising spot, anyway.
I do see differences at around `0x170`:
```
Ultramarine Captain: 00 00 00 00 00 00 00 00 50 03 00 00 00 00 00 00
Chaos Lord: 00 00 00 00 11 01 00 00 47 03 00 04 00 00 02 00
```
Maybe they're somewhat relevant, it's hard to say.
Thingies next: these aren't decoded at all yet, and the sizes seem to be Thingies next: these aren't decoded at all yet, and the sizes seem to be
variable. variable.

39
go.mod
View File

@@ -1,22 +1,33 @@
module code.ur.gs/lupine/ordoor module code.ur.gs/lupine/ordoor
go 1.14 go 1.22.0
toolchain go1.23.2
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v1.4.0
github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065 github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065
github.com/hajimehoshi/ebiten v1.11.1 github.com/hajimehoshi/ebiten/v2 v2.8.2
github.com/jfreymuth/oggvorbis v1.0.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/samuel/go-pcx v0.0.0-20210515040514-6a5ce4d132f7
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4 github.com/stretchr/testify v1.9.0
github.com/stretchr/testify v1.5.1 golang.org/x/image v0.21.0
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
golang.org/x/mobile v0.0.0-20200329125638-4c31acba0007 // indirect )
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect require (
gopkg.in/restruct.v1 v1.0.0-20190323193435-3c2afb705f3c github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/gomobile v0.0.0-20241016134836-cc2e38a7c0ee // indirect
github.com/ebitengine/hideconsole v1.0.0 // indirect
github.com/ebitengine/oto/v3 v3.3.1 // indirect
github.com/ebitengine/purego v0.8.1 // indirect
github.com/jezek/xgb v1.1.1 // indirect
github.com/jfreymuth/oggvorbis v1.0.5 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sync v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

141
go.sum
View File

@@ -1,117 +1,48 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/gomobile v0.0.0-20241016134836-cc2e38a7c0ee h1:YoNt0DHeZ92kjR78SfyUn1yEf7KnBypOFlFZO14cJ6w=
github.com/ebitengine/gomobile v0.0.0-20241016134836-cc2e38a7c0ee/go.mod h1:ZDIonJlTRW7gahIn5dEXZtN4cM8Qwtlduob8cOCflmg=
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
github.com/ebitengine/oto/v3 v3.3.1 h1:d4McwGQuXOT0GL7bA5g9ZnaUEIEjQvG3hafzMy+T3qE=
github.com/ebitengine/oto/v3 v3.3.1/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065 h1:7QVNyw2v9R1qOvbe9vfeVJWWKCSnd2Ap+8l8/CtG9LM= github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065 h1:7QVNyw2v9R1qOvbe9vfeVJWWKCSnd2Ap+8l8/CtG9LM=
github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065/go.mod h1:uN4GbWHfit2ByfOKQ4K6fuLy1/Os2eLynsIrDvjiDgM= github.com/emef/bitfield v0.0.0-20170503144143-7d3f8f823065/go.mod h1:uN4GbWHfit2ByfOKQ4K6fuLy1/Os2eLynsIrDvjiDgM=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE= github.com/hajimehoshi/ebiten/v2 v2.8.2 h1:cvZ5d3LSVFzvcSZVGjTPyV43DzWzJWbwy1b+2V5zJPI=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/hajimehoshi/ebiten/v2 v2.8.2/go.mod h1:SXx/whkvpfsavGo6lvZykprerakl+8Uo1X8d2U5aAnA=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= github.com/jfreymuth/oggvorbis v1.0.5 h1:u+Ck+R0eLSRhgq8WTmffYnrVtSztJcYrl588DM4e3kQ=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/jfreymuth/oggvorbis v1.0.5/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
github.com/hajimehoshi/bitmapfont v1.2.0/go.mod h1:h9QrPk6Ktb2neObTlAbma6Ini1xgMjbJ3w7ysmD7IOU= github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5 h1:hke9UdXY1YPfqjXG1bCSZnoVnfVBw9SzvmlrRn3dL3w=
github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200101150127-38815ba801a5/go.mod h1:0SLvfr8iI2NxzpNB/olBM+dLN9Ur5a9szG13wOgQ0nQ=
github.com/hajimehoshi/ebiten v1.11.0 h1:+pIxfzfVgRbHGM7wBAJtgzPiWiZopA7lyIKNQqc9amk=
github.com/hajimehoshi/ebiten v1.11.0/go.mod h1:aDEhx0K9gSpXw3Cxf2hCXDxPSoF8vgjNqKxrZa/B4Dg=
github.com/hajimehoshi/ebiten v1.11.1 h1:7gy2bHBDNtfTh3GlcUAilk3lNWW9fTLaP7iZAodS9F8=
github.com/hajimehoshi/ebiten v1.11.1/go.mod h1:aDEhx0K9gSpXw3Cxf2hCXDxPSoF8vgjNqKxrZa/B4Dg=
github.com/hajimehoshi/go-mp3 v0.2.1 h1:DH4ns3cPv39n3cs8MPcAlWqPeAwLCK8iNgqvg0QBWI8=
github.com/hajimehoshi/go-mp3 v0.2.1/go.mod h1:Rr+2P46iH6PwTPVgSsEwBkon0CK5DxCAeX/Rp65DCTE=
github.com/hajimehoshi/oto v0.3.4/go.mod h1:PgjqsBJff0efqL2nlMJidJgVJywLn6M4y8PI4TfeWfA=
github.com/hajimehoshi/oto v0.5.4 h1:Dn+WcYeF310xqStKm0tnvoruYUV5Sce8+sfUaIvWGkE=
github.com/hajimehoshi/oto v0.5.4/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/jakecoffman/cp v0.1.0/go.mod h1:a3xPx9N8RyFAACD644t2dj/nK4SuLg1v+jL61m2yVo4=
github.com/jfreymuth/oggvorbis v1.0.0 h1:aOpiihGrFLXpsh2osOlEvTcg5/aluzGQeC7m3uYWOZ0=
github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
github.com/jfreymuth/oggvorbis v1.0.1 h1:NT0eXBgE2WHzu6RT/6zcb2H10Kxj6Fm3PccT0LE6bqw=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
github.com/jfreymuth/vorbis v1.0.0 h1:SmDf783s82lIjGZi8EGUUaS7YxPHgRj4ZXW/h7rUi7U=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe h1:ewr1srjRCmcQogPQ/NCx6XCk6LGVmsVCc9Y3vvPZj+Y= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200521075829-a4cb8d33dbbe/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4 h1:Y/KOCu+ZLB730PudefxfsKVjtI0m0RhvFk9a0l4O1+c= github.com/samuel/go-pcx v0.0.0-20210515040514-6a5ce4d132f7 h1:WhAiClm3vGzSl2EWdFsCFBEu2jEhHGa8qGsz4iIEpRc=
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4/go.mod h1:qxuIawynlRhuaHowuXvd1xjyFWx87Ro4gkZlKRXtHnQ= github.com/samuel/go-pcx v0.0.0-20210515040514-6a5ce4d132f7/go.mod h1:8ofl4LzpDayZKQZYbUyCDW41Y6lgVoO02ABp57OASxY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
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-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20191025110607-73ccc5ba0426 h1:8RjY2wWN6kjy6JvJjDPT51tx4ht4+ldy/a5Yw0AyEr4=
golang.org/x/mobile v0.0.0-20191025110607-73ccc5ba0426/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ=
golang.org/x/mobile v0.0.0-20200222142934-3c8601c510d0/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20200329125638-4c31acba0007 h1:JxsyO7zPDWn1rBZW8FV5RFwCKqYeXnyaS/VQPLpXu6I=
golang.org/x/mobile v0.0.0-20200329125638-4c31acba0007/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
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.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190909214602-067311248421/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191026034945-b2104f82a97d/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-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/restruct.v1 v1.0.0-20190323193435-3c2afb705f3c h1:7j7Yy/3gedviEts3jKY0bEruQkTFKh+8pDmEFaM6UBc=
gopkg.in/restruct.v1 v1.0.0-20190323193435-3c2afb705f3c/go.mod h1:WJaLhyHHEQFOgwIxu/SJxvUHJA18glYsMETBTMIySTY=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -1,6 +1,9 @@
package assetstore package assetstore
import ( import (
"fmt"
"code.ur.gs/lupine/ordoor/internal/data"
"code.ur.gs/lupine/ordoor/internal/idx" "code.ur.gs/lupine/ordoor/internal/idx"
) )
@@ -46,24 +49,37 @@ func (a *AssetStore) AnimationsObject() (*Object, error) {
return obj, nil return obj, nil
} }
func (a *AssetStore) Animation(groupIdx, recIdx int) (*Animation, error) { func (a *AssetStore) Animation(groupIdx int, recId int, compass int) (*Animation, error) {
idx, err := a.AnimationsIndex() realIdx, err := a.AnimationsIndex()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// FIXME: are we using the right value if we need to make this change?
if compass == 0 {
compass = 8
}
obj, err := a.AnimationsObject() obj, err := a.AnimationsObject()
if err != nil { if err != nil {
return nil, err return nil, err
} }
group := idx.Groups[groupIdx] group := realIdx.Groups[groupIdx]
if group.Spec.Count == 0 { if group.Spec.Count == 0 {
return &Animation{}, nil return &Animation{}, nil
} }
// rec := group.Records[recIdx] var det *idx.Detail
det := group.Details[recIdx] for i, rec := range group.Records {
if /*int(rec.ActionID) == int(action) && */ int(rec.Compass) == compass {
det = &group.Details[i]
}
}
if det == nil {
return nil, fmt.Errorf("Couldn't find anim (%v %v %v)", groupIdx, recId, compass)
}
first := int(group.Spec.SpriteIdx) + int(det.FirstSprite) first := int(group.Spec.SpriteIdx) + int(det.FirstSprite)
last := int(group.Spec.SpriteIdx) + int(det.LastSprite) last := int(group.Spec.SpriteIdx) + int(det.LastSprite)
@@ -76,3 +92,48 @@ func (a *AssetStore) Animation(groupIdx, recIdx int) (*Animation, error) {
return &Animation{Frames: sprites}, nil return &Animation{Frames: sprites}, nil
} }
func (a *AssetStore) CharacterAnimation(ctype data.CharacterType, action data.AnimAction, compass int) (*Animation, error) {
ha, err := a.HasAction()
if err != nil {
return nil, err
}
if !ha.Check(ctype, action) {
return nil, fmt.Errorf("character %s: animation %s: not available", ctype, action)
}
// FIXME: we still need to be able to go from CTYPE to GROUP. In particular,
// squad leaders seem to be a modification on top of a previous group, which
// is a bit awkward. For now, hardcode it. How are captain modifiers stored?
group, ok := map[data.CharacterType]int{
data.CharacterTypeTactical: 1, // Has captain
data.CharacterTypeAssault: 3, // Has captain
data.CharacterTypeDevastator: 5,
data.CharacterTypeTerminator: 6, // Has captain
data.CharacterTypeApothecary: 8,
data.CharacterTypeTechmarine: 9,
data.CharacterTypeChaplain: 10,
data.CharacterTypeLibrarian: 11,
data.CharacterTypeCaptain: 12,
data.CharacterTypeChaosMarine: 13,
data.CharacterTypeChaosLord: 14,
data.CharacterTypeChaosChaplain: 15,
data.CharacterTypeChaosSorcerer: 16,
data.CharacterTypeChaosTerminator: 17,
data.CharacterTypeKhorneBerserker: 18,
data.CharacterTypeBloodThirster: 19, // This is a rotating thing?
data.CharacterTypeBloodLetter: 20,
data.CharacterTypeFleshHound: 21,
data.CharacterTypeLordOfChange: 22, // Another rotating thing?
data.CharacterTypeFlamer: 23,
data.CharacterTypePinkHorror: 24,
data.CharacterTypeBlueHorror: 25,
data.CharacterTypeChaosCultist: 26,
}[ctype]
if !ok {
return nil, fmt.Errorf("Unknown character type: %s", ctype)
}
return a.Animation(group, int(action), compass)
}

View File

@@ -8,7 +8,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
"code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/data"
@@ -45,6 +45,7 @@ type AssetStore struct {
cursors map[CursorName]*Cursor cursors map[CursorName]*Cursor
fonts map[string]*Font fonts map[string]*Font
generic *data.Generic generic *data.Generic
hasAction *data.HasAction
idx *idx.Idx idx *idx.Idx
images map[string]*ebiten.Image images map[string]*ebiten.Image
maps map[string]*Map maps map[string]*Map
@@ -111,6 +112,8 @@ func (a *AssetStore) Refresh() error {
a.cursors = make(map[CursorName]*Cursor) a.cursors = make(map[CursorName]*Cursor)
a.entries = newEntryMap a.entries = newEntryMap
a.fonts = make(map[string]*Font) a.fonts = make(map[string]*Font)
a.generic = nil
a.hasAction = nil
a.idx = nil a.idx = nil
a.images = make(map[string]*ebiten.Image) a.images = make(map[string]*ebiten.Image)
a.maps = make(map[string]*Map) a.maps = make(map[string]*Map)

View File

@@ -3,7 +3,7 @@ package assetstore
import ( import (
"image" "image"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
) )
// These are just offsets into the Cursors.cur file // These are just offsets into the Cursors.cur file

View File

@@ -66,6 +66,26 @@ func (a *AssetStore) DefaultOptions() (*config.Options, error) {
return cfg, nil return cfg, nil
} }
func (a *AssetStore) HasAction() (*data.HasAction, error) {
if a.hasAction != nil {
return a.hasAction, nil
}
filename, err := a.lookup("HasAction", "dat", "Data")
if err != nil {
return nil, err
}
hasAction, err := data.LoadHasAction(filename)
if err != nil {
return nil, err
}
a.hasAction = hasAction
return hasAction, nil
}
func intToBool(i int) bool { func intToBool(i int) bool {
return i > 0 return i > 0
} }

View File

@@ -4,7 +4,7 @@ import (
"image" "image"
"os" "os"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
_ "github.com/samuel/go-pcx/pcx" // PCX support _ "github.com/samuel/go-pcx/pcx" // PCX support
) )
@@ -32,10 +32,7 @@ func (a *AssetStore) Image(name string) (*ebiten.Image, error) {
return nil, err return nil, err
} }
img, err := ebiten.NewImageFromImage(rawImg, ebiten.FilterDefault) img := ebiten.NewImageFromImage(rawImg)
if err != nil {
return nil, err
}
a.images[name] = img a.images[name] = img
return img, nil return img, nil

View File

@@ -5,6 +5,7 @@ import (
"image" "image"
"log" "log"
"code.ur.gs/lupine/ordoor/internal/data"
"code.ur.gs/lupine/ordoor/internal/maps" "code.ur.gs/lupine/ordoor/internal/maps"
) )
@@ -101,22 +102,27 @@ func (m *Map) SpritesForCell(x, y, z int) ([]*Sprite, error) {
sprites = append(sprites, sprite) sprites = append(sprites, sprite)
} }
if chr := m.CharacterAt(x, y, z); chr != nil {
// FIXME: this just marks character positions with sprite 19 for now. // Look up the correct animation, get the frame, boom shakalaka
specialsObj, err := m.assets.Object("specials") anim, err := m.assets.CharacterAnimation(chr.Type, data.AnimActionNone, int(chr.Orientation))
if err != nil { if err != nil {
return nil, err return nil, err
}
chrSpr, err := specialsObj.Sprite(19)
if err != nil {
return nil, err
}
for _, chr := range m.raw.Characters {
if chr.XPos == x && chr.YPos == y && z == 1 { // FIXME: sort out ZPos
sprites = append(sprites, chrSpr)
} }
sprites = append(sprites, anim.Frames[0])
} }
return sprites, nil return sprites, nil
} }
func (m *Map) CharacterAt(x, y, z int) *maps.Character {
// FIXME: don't iterate
for i, _ := range m.raw.Characters {
chr := &m.raw.Characters[i]
if chr.XPos == x && chr.YPos == y && z == 0 { // FIXME: sort out ZPos
return chr
}
}
return nil
}

View File

@@ -3,7 +3,7 @@ package assetstore
import ( import (
"log" "log"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/menus" "code.ur.gs/lupine/ordoor/internal/menus"
) )

View File

@@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/data"
) )
@@ -117,10 +117,7 @@ func (o *Object) Sprite(idx int) (*Sprite, error) {
} }
raw := o.raw.Sprites[idx] raw := o.raw.Sprites[idx]
img, err := ebiten.NewImageFromImage(raw.ToImage(o.assets.Palette), ebiten.FilterDefault) img := ebiten.NewImageFromImage(raw.ToImage(o.assets.Palette))
if err != nil {
return nil, err
}
rect := image.Rect( rect := image.Rect(
int(raw.XOffset), int(raw.XOffset),

View File

@@ -4,8 +4,8 @@ import (
"log" "log"
"os" "os"
"github.com/hajimehoshi/ebiten/audio" "github.com/hajimehoshi/ebiten/v2/audio"
"github.com/hajimehoshi/ebiten/audio/vorbis" "github.com/hajimehoshi/ebiten/v2/audio/vorbis"
) )
type Sound struct { type Sound struct {
@@ -57,7 +57,7 @@ func (s *Sound) InfinitePlayer() (*audio.Player, error) {
return nil, err return nil, err
} }
infinite := audio.NewInfiniteLoop(decoder, decoder.Size()) infinite := audio.NewInfiniteLoop(decoder, decoder.Length())
return audio.NewPlayer(audio.CurrentContext(), infinite) return audio.NewPlayer(audio.CurrentContext(), infinite)
} }

View File

@@ -35,7 +35,7 @@ const (
AnimActionRun AnimAction = 14 AnimActionRun AnimAction = 14
AnimActionCrouch AnimAction = 15 AnimActionCrouch AnimAction = 15
AnimActionStand AnimAction = 16 AnimActionStand AnimAction = 16
AnimActionStandingRead AnimAction = 17 AnimActionStandingReady AnimAction = 17
AnimActionStandingUnready AnimAction = 18 AnimActionStandingUnready AnimAction = 18
AnimActionCrouchingReady AnimAction = 19 AnimActionCrouchingReady AnimAction = 19
AnimActionCrouchingUnready AnimAction = 20 AnimActionCrouchingUnready AnimAction = 20
@@ -94,6 +94,89 @@ type HasAction struct {
bits bitfield.BitField bits bitfield.BitField
} }
var (
aActions = map[AnimAction]string{
AnimActionNone: "None",
AnimActionAnim: "Anim",
AnimActionWalk: "Walk",
AnimActionExplosion: "Explosion",
AnimActionProjectile: "Projectile",
AnimActionSmoke: "Smoke",
AnimActionStandingShoot: "Standing Shoot",
AnimActionStandingDeath: "Standing Death",
AnimActionPain: "Pain",
AnimActionSpellFx1: "Spell FX 1",
AnimActionSpellFx2: "Spell FX 2",
AnimActionSpellFx3: "Spell FX 3",
AnimActionSpellFx4: "Spell FX 4",
AnimActionSpellFx5: "Spell FX 5",
AnimActionRun: "Run",
AnimActionCrouch: "Crouch",
AnimActionStand: "Stand",
AnimActionStandingReady: "Standing Ready",
AnimActionStandingUnready: "Standing Unready",
AnimActionCrouchingReady: "Crouching Ready",
AnimActionCrouchingUnready: "Crouching Unready",
AnimActionCrouchingShoot: "Crouching Shoot",
AnimActionStandingGrenade: "Standing Grenade",
AnimActionCrouchingGrenade: "Crouching Grenade",
AnimActionDrawMelee: "Draw Melee",
AnimActionSlash: "Slash",
AnimActionStab: "Stab",
AnimActionBlown: "Blown",
AnimActionCrouchingDeath: "Crouching Death",
AnimActionJump: "Jump",
AnimActionHeal: "Heal",
AnimActionTechWork: "Tech Work",
AnimActionCast: "Cast",
AnimActionShoot: "Shoot",
AnimActionDeath: "Death",
AnimActionFromWarp: "From Warp",
}
cTypes = map[CharacterType]string{
CharacterTypeTactical: "Tactical",
CharacterTypeAssault: "Assault",
CharacterTypeDevastator: "Devastator",
CharacterTypeTerminator: "Terminator",
CharacterTypeApothecary: "Apothecary",
CharacterTypeTechmarine: "Techmarine",
CharacterTypeChaplain: "Chaplain",
CharacterTypeLibrarian: "Librarian",
CharacterTypeCaptain: "Captain",
CharacterTypeChaosMarine: "Chaos Marine",
CharacterTypeChaosLord: "Chaos Lord",
CharacterTypeChaosChaplain: "Chaos Chaplain",
CharacterTypeChaosSorcerer: "Chaos Sorcerer",
CharacterTypeChaosTerminator: "Chaos Terminator",
CharacterTypeKhorneBerserker: "Knorne Berserker",
CharacterTypeBloodThirster: "Bloodthirster",
CharacterTypeBloodLetter: "Bloodletter",
CharacterTypeFleshHound: "Flesh Hound",
CharacterTypeLordOfChange: "Lord of Change",
CharacterTypeFlamer: "Flamer",
CharacterTypePinkHorror: "Pink Horror",
CharacterTypeBlueHorror: "Blue Horror",
CharacterTypeChaosCultist: "Cultist",
}
)
func (a AnimAction) String() string {
if str, ok := aActions[a]; ok {
return str
}
return "Unknown Action"
}
func (c CharacterType) String() string {
if str, ok := cTypes[c]; ok {
return str
}
return "Unknown Character"
}
func LoadHasAction(filename string) (*HasAction, error) { func LoadHasAction(filename string) (*HasAction, error) {
scanner, err := asciiscan.New(filename) scanner, err := asciiscan.New(filename)
if err != nil { if err != nil {
@@ -161,6 +244,17 @@ func (h *HasAction) Actions(c CharacterType) []AnimAction {
return out return out
} }
// FIXME: Too slow
func (h *HasAction) Index(c CharacterType, requestedAction AnimAction) int {
for i, action := range h.Actions(c) {
if action == requestedAction {
return i
}
}
return -1
}
func (h *HasAction) Print() { func (h *HasAction) Print() {
fmt.Println(" Tac Ass Dev Term Apo Tech Chp Lib Cpt CMar CLrd CChp CSrc CTrm Kbz BTh BL FHnd LoC Flm PHr BHr Cult") fmt.Println(" Tac Ass Dev Term Apo Tech Chp Lib Cpt CMar CLrd CChp CSrc CTrm Kbz BTh BL FHnd LoC Flm PHr BHr Cult")
for a := AnimActionStart; a <= AnimActionEnd; a++ { for a := AnimActionStart; a <= AnimActionEnd; a++ {

View File

@@ -6,7 +6,8 @@ import (
"log" "log"
"strings" "strings"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
@@ -92,6 +93,11 @@ func New(assets *assetstore.AssetStore, config *config.Config, ship *ship.Ship)
return out, out.exit return out, out.exit
} }
func (f *Flow) SetScenario(scenario *scenario.Scenario) {
f.current = f.drivers[mainGame]
f.scenario = scenario
}
func (f *Flow) Update(screenX, screenY int) error { func (f *Flow) Update(screenX, screenY int) error {
if f.exit != nil { if f.exit != nil {
return f.exit return f.exit
@@ -113,6 +119,14 @@ func (f *Flow) Update(screenX, screenY int) error {
if ebiten.IsKeyPressed(ebiten.KeyDown) { if ebiten.IsKeyPressed(ebiten.KeyDown) {
f.scenario.Viewpoint.Y += step f.scenario.Viewpoint.Y += step
} }
if inpututil.IsMouseButtonJustReleased(ebiten.MouseButtonLeft) {
f.scenario.SelectHighlightedCharacter()
// Now we need to update the info screens with data about the
// selected character. FIXME: oh, for data binding
f.selectedMainGameCharacter(f.scenario.SelectedCharacter())
}
} }
if f.scenario != nil { if f.scenario != nil {

View File

@@ -1,5 +1,9 @@
package flow package flow
import (
"code.ur.gs/lupine/ordoor/internal/maps"
)
// TODO: There are Chaos and Ultramarine versions of MainGame. Do we really want // TODO: There are Chaos and Ultramarine versions of MainGame. Do we really want
// to duplicate everything for both? // to duplicate everything for both?
@@ -16,7 +20,7 @@ func (f *Flow) linkMainGame() {
}) })
// 8: Character stats // 8: Character stats
f.onClick(mainGame, "8.21", func() { // Stat more buttont f.onClick(mainGame, "8.21", func() { // Stat more buttons
f.setActiveNow(mainGame, "7", true) f.setActiveNow(mainGame, "7", true)
f.setActiveNow(mainGame, "8", false) f.setActiveNow(mainGame, "8", false)
}) })
@@ -154,3 +158,48 @@ func (f *Flow) linkMainGameViewMenu() {
})) }))
} }
func (f *Flow) maybeSetErr(next func() error) {
if f.exit != nil {
return
}
f.exit = next()
}
func (f *Flow) selectedMainGameCharacter(chr *maps.Character) {
if chr == nil {
chr = &maps.Character{}
}
d := f.drivers[mainGame]
// 7.1 Portrait
f.maybeSetErr(func() error { return d.SetValue("7.2", chr.Name) }) // Name
// 7.3 doesn't exit
// 7.4 more button (ignore)
// 7.5 AP icon
// f.maybeSetErr(func() error { return d.SetValueInt("7.6", chr.ActionPoints)}) // AP meter
f.maybeSetErr(func() error { return d.SetValueInt("7.7", chr.ActionPoints) }) // AP value
// 7.8 armor icon
// 7.9 armor meter
f.maybeSetErr(func() error { return d.SetValueInt("7.10", chr.Armor) }) // armor value
// 7.11 health icon
// 7.12 health meter
f.maybeSetErr(func() error { return d.SetValueInt("7.13", chr.Health) }) // health value
// 7.14 action points status bar
// 7.15 armor status bar
// 7.16 health status bar
// 8.1 to 8.10 are hot spots
f.maybeSetErr(func() error { return d.SetValueInt("8.11", chr.ActionPoints) }) // AP
f.maybeSetErr(func() error { return d.SetValueInt("8.12", chr.Health) }) // Health
f.maybeSetErr(func() error { return d.SetValueInt("8.13", chr.Armor) }) // Armor
f.maybeSetErr(func() error { return d.SetValueInt("8.14", chr.BallisticSkill) }) // Ballistic Skill
f.maybeSetErr(func() error { return d.SetValueInt("8.15", chr.WeaponSkill) }) // Weapon Skill
f.maybeSetErr(func() error { return d.SetValueInt("8.16", chr.Strength) }) // Strength
f.maybeSetErr(func() error { return d.SetValueInt("8.17", chr.Toughness) }) // Toughness
// 8.18 Initiative
// 8.19 Attacks
f.maybeSetErr(func() error { return d.SetValueInt("8.20", chr.Leadership) }) // Leadership
}

View File

@@ -12,6 +12,8 @@ import (
"strings" "strings"
"github.com/lunixbochs/struc" "github.com/lunixbochs/struc"
"code.ur.gs/lupine/ordoor/internal/data"
) )
var ( var (
@@ -99,8 +101,9 @@ type Cell struct {
} }
type Character struct { type Character struct {
Unknown1 []byte `struc:"[179]byte"` Unknown1 []byte `struc:"[178]byte"`
Name string `struc:"[80]byte"` Type data.CharacterType `struc:"byte"`
Name string `struc:"[80]byte"`
// Attributes guessed by matching up numbers. Starts at 0x103 // Attributes guessed by matching up numbers. Starts at 0x103
WeaponSkill int `struc:"byte"` WeaponSkill int `struc:"byte"`
@@ -121,7 +124,9 @@ type Character struct {
XPos int `struc:"byte"` XPos int `struc:"byte"`
Unknown7 []byte `struc:"[317]byte"` Unknown7 []byte `struc:"[317]byte"`
SquadNumber byte `struc:"byte"` SquadNumber byte `struc:"byte"`
Unknown8 []byte `struc:"[927]byte"` Unknown8 []byte `struc:"[895]byte"`
Orientation byte `struc:"byte"`
Unknown9 []byte `struc:"[31]byte"`
// TODO: each character may have a fixed number of subrecords for inventory // TODO: each character may have a fixed number of subrecords for inventory
} }
@@ -316,7 +321,8 @@ func loadMapFile(filename string) (*GameMap, error) {
nullTerminate(&out.Title) nullTerminate(&out.Title)
nullTerminate(&out.Briefing) nullTerminate(&out.Briefing)
for i, chr := range out.Characters { for i, _ := range out.Characters {
chr := &out.Characters[i]
nullTerminate(&chr.Name) nullTerminate(&chr.Name)
fmt.Printf("Character %v: %s\n", i, chr.String()) fmt.Printf("Character %v: %s\n", i, chr.String())
} }
@@ -339,9 +345,11 @@ func nullTerminate(s *string) {
func (c *Character) String() string { func (c *Character) String() string {
return fmt.Sprintf( return fmt.Sprintf(
"squad=%v pos=(%v,%v) name=%q\n\t%3d %3d %3d %3d %3d\n\t%3d %3d ??? ??? %3d\n", "squad=%v pos=(%v,%v) type=%q name=%q\n"+
"\t%3d %3d %3d %3d %3d\n\t%3d %3d ??? ??? %3d\n",
c.SquadNumber, c.SquadNumber,
c.XPos, c.YPos, c.XPos, c.YPos,
c.Type.String(),
c.Name, c.Name,
c.ActionPoints, c.Health, c.Armor, c.BallisticSkill, c.WeaponSkill, c.ActionPoints, c.Health, c.Armor, c.BallisticSkill, c.WeaponSkill,
c.Strength, c.Toughness /*c.Initiative, c.Attacks,*/, c.Leadership, c.Strength, c.Toughness /*c.Initiative, c.Attacks,*/, c.Leadership,

View File

@@ -33,7 +33,7 @@ const (
SubTypeLineBriefing SubMenuType = 41 SubTypeLineBriefing SubMenuType = 41
SubTypeThumb SubMenuType = 45 // A "thumb" appears to be a vertical slider SubTypeThumb SubMenuType = 45 // A "thumb" appears to be a vertical slider
SubTypeInvokeButton SubMenuType = 50 SubTypeInvokeButton SubMenuType = 50
SubTypeDoorHotspot3 SubMenuType = 60 // Maybe? Appears in Arrange.mnu SubTypeClickText SubMenuType = 60
SubTypeOverlay SubMenuType = 61 SubTypeOverlay SubMenuType = 61
SubTypeHypertext SubMenuType = 70 SubTypeHypertext SubMenuType = 70
SubTypeCheckbox SubMenuType = 91 SubTypeCheckbox SubMenuType = 91

View File

@@ -7,10 +7,11 @@ package ordoor
import ( import (
"fmt" "fmt"
"log" "log"
"sync"
"time" "time"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/audio" "github.com/hajimehoshi/ebiten/v2/audio"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/config"
@@ -28,7 +29,8 @@ type Ordoor struct {
win *ui.Window win *ui.Window
// Relevant to interface state // Relevant to interface state
flow *flow.Flow flow *flow.Flow
flowOnce sync.Once
// FIXME: should be put inside flow // FIXME: should be put inside flow
// If this is set, we display it instead of flow // If this is set, we display it instead of flow
@@ -61,9 +63,7 @@ func Run(configFile string, overrideX, overrideY int) error {
} }
} }
if _, err := audio.NewContext(48000); err != nil { _ = audio.NewContext(48000)
return fmt.Errorf("Failed to set up audio context: %v", err)
}
ordoor := &Ordoor{ ordoor := &Ordoor{
assets: assets, assets: assets,
@@ -180,6 +180,7 @@ func (o *Ordoor) Update(screenX, screenY int) error {
} }
func (o *Ordoor) Draw(screen *ebiten.Image) error { func (o *Ordoor) Draw(screen *ebiten.Image) error {
if pic := o.pic; pic != nil { if pic := o.pic; pic != nil {
// Scale the picture to the screen and draw it // Scale the picture to the screen and draw it
scaleX := float64(screen.Bounds().Dx()) / float64(pic.Bounds().Dx()) scaleX := float64(screen.Bounds().Dx()) / float64(pic.Bounds().Dx())
@@ -188,10 +189,15 @@ func (o *Ordoor) Draw(screen *ebiten.Image) error {
do := &ebiten.DrawImageOptions{} do := &ebiten.DrawImageOptions{}
do.GeoM.Scale(scaleX, scaleY) do.GeoM.Scale(scaleX, scaleY)
return screen.DrawImage(pic, do) screen.DrawImage(pic, do)
return nil
} }
return o.flow.Draw(screen) if o.flow != nil {
return o.flow.Draw(screen)
}
return nil // Draw() may be called before Update()
} }
func (o *Ordoor) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) { func (o *Ordoor) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {

View File

@@ -5,8 +5,8 @@ import (
"image" "image"
"sort" "sort"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
) )
type CartPt struct { type CartPt struct {
@@ -36,7 +36,7 @@ func (s *Scenario) Update(screenX, screenY int) error {
} }
// FIXME: adjust for Z level // FIXME: adjust for Z level
s.selectedCell = screenPos.ToISO() s.highlightedCell = screenPos.ToISO()
return nil return nil
} }
@@ -51,13 +51,13 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
sw, sh := screen.Size() sw, sh := screen.Size()
topLeft := CartPt{ topLeft := CartPt{
X: float64(s.Viewpoint.X) - (2*cellWidth/s.Zoom), // Ensure all visible cells are rendered X: float64(s.Viewpoint.X) - (2 * cellWidth / s.Zoom), // Ensure all visible cells are rendered
Y: float64(s.Viewpoint.Y) - (2*cellHeight/s.Zoom), Y: float64(s.Viewpoint.Y) - (2 * cellHeight / s.Zoom),
}.ToISO() }.ToISO()
bottomRight := CartPt{ bottomRight := CartPt{
X: float64(s.Viewpoint.X) + (float64(sw)/s.Zoom) + (2*cellHeight/s.Zoom), X: float64(s.Viewpoint.X) + (float64(sw) / s.Zoom) + (2 * cellHeight / s.Zoom),
Y: float64(s.Viewpoint.Y) + (float64(sh)/s.Zoom) + (5*cellHeight/s.Zoom), // Z dimension requires it Y: float64(s.Viewpoint.Y) + (float64(sh) / s.Zoom) + (5 * cellHeight / s.Zoom), // Z dimension requires it
}.ToISO() }.ToISO()
// X+Y is constant for all tiles in a column // X+Y is constant for all tiles in a column
@@ -95,17 +95,15 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
return false return false
}) })
counter := map[string]int{} counter := 0
for _, pt := range toDraw { for _, pt := range toDraw {
for z := 0; z <= s.ZIdx; z++ { for z := 0; z <= s.ZIdx; z++ {
if err := s.renderCell(int(pt.X), int(pt.Y), z, screen, counter); err != nil { if err := s.renderCell(int(pt.X), int(pt.Y), z, screen, &counter); err != nil {
return err return err
} }
} }
} }
//log.Printf("%#+v", counter)
// Finally, draw cursor chrome // Finally, draw cursor chrome
// FIXME: it looks like we might need to do this in normal painting order... // FIXME: it looks like we might need to do this in normal painting order...
spr, err := s.specials.Sprite(0) spr, err := s.specials.Sprite(0)
@@ -114,31 +112,31 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
} }
op := ebiten.DrawImageOptions{} op := ebiten.DrawImageOptions{}
geo := s.geoForCoords(int(s.selectedCell.X), int(s.selectedCell.Y), 0) geo := s.geoForCoords(int(s.highlightedCell.X), int(s.highlightedCell.Y), 0)
op.GeoM = geo op.GeoM = geo
op.GeoM.Translate(-209, -332) op.GeoM.Translate(-209, -332)
op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y)) op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y))
op.GeoM.Scale(s.Zoom, s.Zoom) op.GeoM.Scale(s.Zoom, s.Zoom)
if err := screen.DrawImage(spr.Image, &op); err != nil { screen.DrawImage(spr.Image, &op)
return err
}
x1, y1 := geo.Apply(0, 0) x1, y1 := geo.Apply(0, 0)
ebitenutil.DebugPrintAt( ebitenutil.DebugPrintAt(
screen, screen,
fmt.Sprintf("(%d,%d)", int(s.selectedCell.X), int(s.selectedCell.Y)), fmt.Sprintf("(%d,%d)", int(s.highlightedCell.X), int(s.highlightedCell.Y)),
int(x1), int(x1),
int(y1), int(y1),
) )
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Sprites: %v", counter), 0, 16)
/* /*
// debug: draw a square around the selected cell // debug: draw a square around the selected cell
x2, y2 := geo.Apply(cellWidth, cellHeight) x2, y2 := geo.Apply(cellWidth, cellHeight)
ebitenutil.DrawLine(screen, x1, y1, x2, y1, colornames.Green) // top line ebitenutil.DrawLine(screen, x1, y1, x2, y1, colornames.Green) // top line
ebitenutil.DrawLine(screen, x1, y1, x1, y2, colornames.Green) // left line ebitenutil.DrawLine(screen, x1, y1, x1, y2, colornames.Green) // left line
ebitenutil.DrawLine(screen, x2, y1, x2, y2, colornames.Green) // right line ebitenutil.DrawLine(screen, x2, y1, x2, y2, colornames.Green) // right line
ebitenutil.DrawLine(screen, x1, y2, x2, y2, colornames.Green) // bottom line ebitenutil.DrawLine(screen, x1, y2, x2, y2, colornames.Green) // bottom line
*/ */
return nil return nil
@@ -164,7 +162,7 @@ func (s *Scenario) geoForCoords(x, y, z int) ebiten.GeoM {
return geo return geo
} }
func (s *Scenario) renderCell(x, y, z int, screen *ebiten.Image, counter map[string]int) error { func (s *Scenario) renderCell(x, y, z int, screen *ebiten.Image, counter *int) error {
sprites, err := s.area.SpritesForCell(x, y, z) sprites, err := s.area.SpritesForCell(x, y, z)
if err != nil { if err != nil {
return err return err
@@ -179,10 +177,7 @@ func (s *Scenario) renderCell(x, y, z int, screen *ebiten.Image, counter map[str
iso.Translate(-209, -332) iso.Translate(-209, -332)
for _, spr := range sprites { for _, spr := range sprites {
// if _, ok := counter[spr.ID]; !ok { *counter = *counter + 1
// counter[spr.ID] = 0
// }
// counter[spr.ID] = counter[spr.ID] + 1
op := ebiten.DrawImageOptions{GeoM: iso} op := ebiten.DrawImageOptions{GeoM: iso}
op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y)) op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y))
@@ -190,9 +185,7 @@ func (s *Scenario) renderCell(x, y, z int, screen *ebiten.Image, counter map[str
// Zoom has to come last // Zoom has to come last
op.GeoM.Scale(s.Zoom, s.Zoom) op.GeoM.Scale(s.Zoom, s.Zoom)
if err := screen.DrawImage(spr.Image, &op); err != nil { screen.DrawImage(spr.Image, &op)
return err
}
} }
return nil return nil

View File

@@ -1,6 +1,8 @@
package scenario package scenario
import ( import (
"log"
"code.ur.gs/lupine/ordoor/internal/maps" "code.ur.gs/lupine/ordoor/internal/maps"
) )
@@ -10,8 +12,23 @@ type CellPoint struct {
} }
func (s *Scenario) CellAtCursor() (*maps.Cell, CellPoint) { func (s *Scenario) CellAtCursor() (*maps.Cell, CellPoint) {
cell := s.area.Cell(int(s.selectedCell.X), int(s.selectedCell.Y), 0) cell := s.area.Cell(int(s.highlightedCell.X), int(s.highlightedCell.Y), 0)
return cell, CellPoint{IsoPt: s.selectedCell, Z: 0} return cell, CellPoint{IsoPt: s.highlightedCell, Z: 0}
}
func (s *Scenario) HighlightedCharacter() *maps.Character {
// FIXME: characters are always at zIdx 0 right now
return s.area.CharacterAt(int(s.highlightedCell.X), int(s.highlightedCell.Y), 0)
}
func (s *Scenario) SelectedCharacter() *maps.Character {
return s.selectedCharacter
}
func (s *Scenario) SelectHighlightedCharacter() {
chr := s.HighlightedCharacter()
log.Printf("Selected character %s", chr)
s.selectedCharacter = chr
} }
func (s *Scenario) ChangeZIdx(by int) { func (s *Scenario) ChangeZIdx(by int) {

View File

@@ -2,19 +2,21 @@
package scenario package scenario
import ( import (
"fmt"
"image" "image"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
"code.ur.gs/lupine/ordoor/internal/maps"
) )
type Scenario struct { type Scenario struct {
area *assetstore.Map area *assetstore.Map
specials *assetstore.Object specials *assetstore.Object
tick int tick int
turn int turn int
selectedCell IsoPt
highlightedCell IsoPt
selectedCharacter *maps.Character
// All these must be modified by user actions somehow. // All these must be modified by user actions somehow.
// TODO: extract into the idea of a viewport passed to Update / Draw somehow? // TODO: extract into the idea of a viewport passed to Update / Draw somehow?
@@ -36,9 +38,9 @@ func NewScenario(assets *assetstore.AssetStore, name string) (*Scenario, error)
} }
// Eager load sprites. TODO: do we really want to do this? // Eager load sprites. TODO: do we really want to do this?
if err := area.LoadSprites(); err != nil { //if err := area.LoadSprites(); err != nil {
return nil, fmt.Errorf("Eager-loading sprites failed: %v", err) // return nil, fmt.Errorf("Eager-loading sprites failed: %v", err)
} //}
out := &Scenario{ out := &Scenario{
area: area, area: area,

View File

@@ -1,7 +1,7 @@
package ui package ui
import ( import (
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
) )
var ( var (

View File

@@ -5,8 +5,8 @@ import (
"image" "image"
"runtime/debug" "runtime/debug"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
"code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/assetstore"
) )
@@ -137,9 +137,7 @@ func (d *Driver) Draw(screen *ebiten.Image) error {
do.GeoM = d.orig2native do.GeoM = d.orig2native
do.GeoM.Translate(x, y) do.GeoM.Translate(x, y)
if err := screen.DrawImage(region.image, &do); err != nil { screen.DrawImage(region.image, &do)
return err
}
} }
} }

View File

@@ -78,8 +78,10 @@ func (d *Driver) buildRecord(r *menus.Record) (*Widget, error) {
switch r.Type { switch r.Type {
case menus.SubTypeSimpleButton, menus.SubTypeInvokeButton: case menus.SubTypeSimpleButton, menus.SubTypeInvokeButton:
_, widget, err = d.buildButton(r.Props()) _, widget, err = d.buildButton(r.Props())
case menus.SubTypeDoorHotspot1, menus.SubTypeDoorHotspot2, menus.SubTypeDoorHotspot3: case menus.SubTypeDoorHotspot1, menus.SubTypeDoorHotspot2:
_, widget, err = d.buildDoorHotspot(r.Props()) _, widget, err = d.buildDoorHotspot(r.Props())
case menus.SubTypeClickText:
_, widget, err = d.buildClickText(r.Props())
case menus.SubTypeOverlay: case menus.SubTypeOverlay:
_, widget, err = d.buildOverlay(r.Props()) _, widget, err = d.buildOverlay(r.Props())
case menus.SubTypeHypertext: case menus.SubTypeHypertext:

View File

@@ -3,7 +3,7 @@ package ui
import ( import (
"image" "image"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
) )
type region struct { type region struct {

View File

@@ -157,9 +157,9 @@ func (l *listBox) refresh() {
// FIXME: noninteractive isn't set up for dynamic text yet. Need to // FIXME: noninteractive isn't set up for dynamic text yet. Need to
// generate textImg on demand instead of once at start. // generate textImg on demand instead of once at start.
if ni.label != nil { if ni.label != nil {
ni.label.text = "" ni.label.str = ""
if len(l.strings) > l.offset+i { if len(l.strings) > l.offset+i {
ni.label.text = l.strings[l.offset+i] ni.label.str = l.strings[l.offset+i]
} }
} }
} }

View File

@@ -32,12 +32,13 @@ type noninteractive struct {
hoverImpl hoverImpl
} }
// Paint some text to screen // Paint some text to screen, possibly settable
type label struct { type label struct {
align AlignMode locator string
rect image.Rectangle align AlignMode
text string rect image.Rectangle
font *assetstore.Font font *assetstore.Font
valueImpl
} }
// This particular animation has entry and exit sequences, which are invoked // This particular animation has entry and exit sequences, which are invoked
@@ -109,6 +110,33 @@ func (d *Driver) buildHypertext(p *menus.Properties) (*noninteractive, *Widget,
return ni, widget, nil return ni, widget, nil
} }
func (d *Driver) buildClickText(p *menus.Properties) (*noninteractive, *Widget, error) {
ni, err := d.buildNoninteractive(p)
if err != nil {
return nil, nil, err
}
fnt := d.menu.Font(p.FontType/10 - 1)
// FIXME: is this always right? Seems to make sense for Main.mnu
ni.label = &label{
locator: ni.locator,
font: fnt,
rect: ni.rect, // We will be centered by default
// Starts with no text. The text specified in the menu is hovertext
}
widget := &Widget{
Locator: ni.locator,
Active: p.Active,
ownClickables: []clickable{ni},
ownPaintables: []paintable{ni},
ownValueables: []valueable{ni.label},
}
return ni, widget, nil
}
// An overlay is a static image + some text that needs to be rendered // An overlay is a static image + some text that needs to be rendered
func (d *Driver) buildOverlay(p *menus.Properties) (*noninteractive, *Widget, error) { func (d *Driver) buildOverlay(p *menus.Properties) (*noninteractive, *Widget, error) {
ni, err := d.buildNoninteractive(p) ni, err := d.buildNoninteractive(p)
@@ -127,9 +155,9 @@ func (d *Driver) buildOverlay(p *menus.Properties) (*noninteractive, *Widget, er
fnt := d.menu.Font(p.FontType/10 - 1) fnt := d.menu.Font(p.FontType/10 - 1)
ni.label = &label{ ni.label = &label{
font: fnt, font: fnt,
rect: ni.rect, // We will be centered by default rect: ni.rect, // We will be centered by default
text: p.Text, valueImpl: valueImpl{str: p.Text},
} }
} else { } else {
log.Printf("Overlay without text detected in %v", p.Locator) log.Printf("Overlay without text detected in %v", p.Locator)
@@ -253,6 +281,10 @@ func (a *animationHover) setHoverState(value bool) {
a.hoverImpl.setHoverState(value) a.hoverImpl.setHoverState(value)
} }
func (l *label) id() string {
return l.locator
}
// Top-left of where to start drawing the text. We want it to appear to be in // Top-left of where to start drawing the text. We want it to appear to be in
// the centre of the rect. // the centre of the rect.
// //
@@ -260,7 +292,7 @@ func (a *animationHover) setHoverState(value bool) {
func (l *label) pos() image.Point { func (l *label) pos() image.Point {
pos := l.rect.Min pos := l.rect.Min
textRect := l.font.CalculateBounds(l.text) textRect := l.font.CalculateBounds(l.str)
// Centre the text horizontally // Centre the text horizontally
if l.align == AlignModeCentre { if l.align == AlignModeCentre {
@@ -287,15 +319,21 @@ func (l *label) regions(tick int) []region {
pt := l.pos() pt := l.pos()
for _, r := range l.text { for _, r := range l.str {
glyph, err := l.font.Glyph(r) var sprite *assetstore.Sprite
if err != nil { if glyph, err := l.font.Glyph(r); err != nil {
log.Printf("FIXME: ignoring misssing glyph %v", r) if glyph, err := l.font.Glyph('?'); err != nil {
continue log.Printf("FIXME: ignoring glyph %v", r)
continue
} else {
sprite = glyph
}
} else {
sprite = glyph
} }
out = append(out, oneRegion(pt, glyph.Image)...) out = append(out, oneRegion(pt, sprite.Image)...)
pt.X += glyph.Rect.Dx() pt.X += sprite.Rect.Dx()
} }
return out return out

View File

@@ -8,9 +8,9 @@ import (
"runtime/debug" "runtime/debug"
"runtime/pprof" "runtime/pprof"
"github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/ebitenutil" "github.com/hajimehoshi/ebiten/v2/ebitenutil"
"github.com/hajimehoshi/ebiten/inpututil" "github.com/hajimehoshi/ebiten/v2/inpututil"
) )
type Game interface { type Game interface {
@@ -53,8 +53,6 @@ type Window struct {
// //
// ebiten assumes a single window, so only call this once... // ebiten assumes a single window, so only call this once...
func NewWindow(game Game, title string, xRes int, yRes int) (*Window, error) { func NewWindow(game Game, title string, xRes int, yRes int) (*Window, error) {
ebiten.SetRunnableInBackground(true)
return &Window{ return &Window{
Title: title, Title: title,
debug: true, debug: true,
@@ -109,10 +107,12 @@ func (w *Window) drawCursor(screen *ebiten.Image) error {
ebiten.SetCursorMode(ebiten.CursorModeHidden) ebiten.SetCursorMode(ebiten.CursorModeHidden)
return screen.DrawImage(cursor, op) screen.DrawImage(cursor, op)
return nil
} }
func (w *Window) Update(screen *ebiten.Image) (outErr error) { func (w *Window) Update() (outErr error) {
// Ebiten does not like it if we panic inside its main loop // Ebiten does not like it if we panic inside its main loop
defer func() { defer func() {
if panicErr := recover(); panicErr != nil { if panicErr := recover(); panicErr != nil {
@@ -124,7 +124,8 @@ func (w *Window) Update(screen *ebiten.Image) (outErr error) {
} }
}() }()
if err := w.game.Update(screen.Size()); err != nil { // FIXME: remove need for update generally
if err := w.game.Update(w.xRes, w.yRes); err != nil {
return err return err
} }
@@ -157,13 +158,11 @@ func (w *Window) Update(screen *ebiten.Image) (outErr error) {
} }
} }
if ebiten.IsDrawingSkipped() { return nil
return nil }
}
if err := w.game.Draw(screen); err != nil { func (w *Window) Draw(screen *ebiten.Image) {
return err w.game.Draw(screen)
}
if w.debug { if w.debug {
// Draw FPS, etc, to the screen // Draw FPS, etc, to the screen
@@ -172,7 +171,7 @@ func (w *Window) Update(screen *ebiten.Image) (outErr error) {
} }
// Draw the cursor last // Draw the cursor last
return w.drawCursor(screen) w.drawCursor(screen)
} }
// TODO: a stop or other cancellation mechanism // TODO: a stop or other cancellation mechanism