Compare commits
4 Commits
6d58d12536
...
7081db42f4
Author | SHA1 | Date | |
---|---|---|---|
7081db42f4 | |||
0bf8233cd1 | |||
c2cbf1d95d | |||
54fe95239e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,4 +5,5 @@
|
|||||||
/SL
|
/SL
|
||||||
/SaW
|
/SaW
|
||||||
/WoW
|
/WoW
|
||||||
|
/WoW-CD
|
||||||
/bin
|
/bin
|
||||||
|
67
README.md
67
README.md
@@ -72,6 +72,10 @@ The menu system seen in Chaos Gate is not present; instead, there is a `BUTTONS`
|
|||||||
directory and a lot of `pcx` files under `PIC` that, I suspect, do the job for
|
directory and a lot of `pcx` files under `PIC` that, I suspect, do the job for
|
||||||
this game.
|
this game.
|
||||||
|
|
||||||
|
Even with a full installation, Wages of War leaves a lot of data on the CD. It
|
||||||
|
may be best to run solely from the `WOW` directory on the CD, assuming it's a
|
||||||
|
strict superset of what gets installed, data-wise.
|
||||||
|
|
||||||
## Long-term goals
|
## Long-term goals
|
||||||
|
|
||||||
Once full playthrough of the official single-player campaign for all four games
|
Once full playthrough of the official single-player campaign for all four games
|
||||||
@@ -96,7 +100,8 @@ Completely new fantasy game using the same engine.
|
|||||||
## Building from source
|
## Building from source
|
||||||
|
|
||||||
I'm writing code in Go at the moment, so you'll need to have a Go runtime
|
I'm writing code in Go at the moment, so you'll need to have a Go runtime
|
||||||
installed on your system:
|
installed on your system. Dependency management uses `go mod`, so ensure you
|
||||||
|
have at least Go 1.11.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go version
|
$ go version
|
||||||
@@ -114,28 +119,25 @@ Debian:
|
|||||||
You can then run `make all` in the source tree to get the binaries that are
|
You can then run `make all` in the source tree to get the binaries that are
|
||||||
present at the moment.
|
present at the moment.
|
||||||
|
|
||||||
Place your WH40K: Chaos Gate installation in `./orig` to benefit from automatic
|
## Configuring
|
||||||
path defaults. Otherwise, point to it with `-game-path`
|
|
||||||
|
|
||||||
The `view-map` binary attempts to render a map, and is the current focus of
|
Since we support multiple games, a fair bit of configuration is required. Copy
|
||||||
effort. Once I can render a whole map, including pre-placed characters (cultist
|
`config.toml.example` to `config.toml` and edit it to your requirements. The
|
||||||
scum), things can start to get more interesting.
|
`data_dir` for the engine(s) you want to use is probably the most important bit,
|
||||||
|
along with the `default_engine`.
|
||||||
|
|
||||||
Current status: almost pixel-perfect map rendering. Static objects (four per map
|
The various games all use snapshots of the original engine at different points
|
||||||
coordinate: floor, centre, left, and right) are rendered fine, and each Z level
|
in time, and specify a lot in code that we need to specify in data. That should
|
||||||
looks good. There are a few minor artifacts here and there.
|
all go into the config file, so new games will be able to adapt the engine to
|
||||||
|
their needs.
|
||||||
|
|
||||||
Characters and animations aren't touched at all yet. Rendering performance is
|
## Running
|
||||||
poor. No gameplay, no campaign logic. Interaction with the play area is minimal
|
|
||||||
and limited to pan, zoom, and click for basic console output.
|
|
||||||
|
|
||||||
Still, I'm proud of myself.
|
|
||||||
|
|
||||||
To run:
|
To run:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ make view-map
|
$ make view-map
|
||||||
$ ./view-map -map Chapter01
|
$ ./bin/view-map -map Chapter01
|
||||||
```
|
```
|
||||||
|
|
||||||
Looks like this:
|
Looks like this:
|
||||||
@@ -145,27 +147,29 @@ Looks like this:
|
|||||||
Use the arrow keys to scroll around the map, the mouse wheel to zoom, and the
|
Use the arrow keys to scroll around the map, the mouse wheel to zoom, and the
|
||||||
`1` - `7` keys to change Z level.
|
`1` - `7` keys to change Z level.
|
||||||
|
|
||||||
Dependency management uses `go mod`, so ensure you have at least Go 1.11.
|
Menus / UI widgets have fairly good support now; you can use the `view-menu`
|
||||||
|
binary to inspect them:
|
||||||
There is the **start** of the menu / campaign flow in a `ordoor` binary:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ cp config.toml.example config.toml
|
|
||||||
$ make ordoor
|
|
||||||
$ ./ordoor
|
|
||||||
```
|
|
||||||
|
|
||||||
This plays the introductory videos so far, and nothing else.
|
|
||||||
|
|
||||||
Menus are in the process of being rendered; you can use the `view-menu` binary
|
|
||||||
to inspect them:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
make view-menu
|
make view-menu
|
||||||
./view-menu -menu ./orig/Menu/Main.mnu
|
./bin/view-menu -menu Main
|
||||||
```
|
```
|
||||||
|
|
||||||
This menu *displays* OK, including
|
This renders the menus found in Chaos Gate and Soldiers At War. The Squad Leader
|
||||||
|
format seems basically the same, but has some extra files and aren't 8-bit
|
||||||
|
colour. They don't display at the moment. Wages of War uses a different format
|
||||||
|
altogether.
|
||||||
|
|
||||||
|
For Chaos Gate, there is the **start** of the game in an `ordoor` binary:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ make ordoor
|
||||||
|
$ ./bin/ordoor
|
||||||
|
```
|
||||||
|
|
||||||
|
The idea is to hook all the different parts together, and to an abstract game
|
||||||
|
state (which is called `ship` for ordoor), to make the whole thing playable. It
|
||||||
|
isn't playable *yet*, but it's heading in that direction.
|
||||||
|
|
||||||
## Sound
|
## Sound
|
||||||
|
|
||||||
@@ -180,4 +184,3 @@ $ ./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.
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@ video_player = ["mpv", "--no-config", "--keep-open=no", "--force-window=no", "--
|
|||||||
default_engine = "ordoor"
|
default_engine = "ordoor"
|
||||||
|
|
||||||
[engines.geas] # Wages of War -> Gifts of Peace -> Geas
|
[engines.geas] # Wages of War -> Gifts of Peace -> Geas
|
||||||
data_dir = "./WoW"
|
data_dir = "./WoW-CD"
|
||||||
palette = "WagesOfWar"
|
palette = "WagesOfWar"
|
||||||
|
|
||||||
[engines.ordoor] # Chaos Gate -> Order Door -> Ordoor
|
[engines.ordoor] # Chaos Gate -> Order Door -> Ordoor
|
||||||
@@ -14,9 +14,9 @@ default_engine = "ordoor"
|
|||||||
data_dir = "./SaW"
|
data_dir = "./SaW"
|
||||||
palette = "SoldiersAtWar"
|
palette = "SoldiersAtWar"
|
||||||
|
|
||||||
# [engines.] Squad Leader -> ??? -> ???
|
[engines.sl] # Squad Leader -> ??? -> ???
|
||||||
# data_dir = "./SL"
|
data_dir = "./SL"
|
||||||
# palette = "SquadLeader" # may not be relevant?
|
palette = "ChaosGate" # may not be relevant?
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
play_movies = true
|
play_movies = true
|
||||||
|
1
go.mod
1
go.mod
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/jfreymuth/oggvorbis v1.0.1 // indirect
|
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
|
github.com/samuel/go-pcx v0.0.0-20180426214139-d9c017170db4
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.5.1
|
||||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect
|
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect
|
||||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1
|
golang.org/x/image v0.0.0-20200119044424-58c23975cae1
|
||||||
|
2
go.sum
2
go.sum
@@ -45,6 +45,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
|
|||||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||||
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-20180426214139-d9c017170db4/go.mod h1:qxuIawynlRhuaHowuXvd1xjyFWx87Ro4gkZlKRXtHnQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
@@ -8,6 +8,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
|
||||||
"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"
|
||||||
"code.ur.gs/lupine/ordoor/internal/idx"
|
"code.ur.gs/lupine/ordoor/internal/idx"
|
||||||
@@ -44,6 +46,7 @@ type AssetStore struct {
|
|||||||
fonts map[string]*Font
|
fonts map[string]*Font
|
||||||
generic *data.Generic
|
generic *data.Generic
|
||||||
idx *idx.Idx
|
idx *idx.Idx
|
||||||
|
images map[string]*ebiten.Image
|
||||||
maps map[string]*Map
|
maps map[string]*Map
|
||||||
menus map[string]*Menu
|
menus map[string]*Menu
|
||||||
objs map[string]*Object
|
objs map[string]*Object
|
||||||
@@ -109,6 +112,7 @@ func (a *AssetStore) Refresh() error {
|
|||||||
a.entries = newEntryMap
|
a.entries = newEntryMap
|
||||||
a.fonts = make(map[string]*Font)
|
a.fonts = make(map[string]*Font)
|
||||||
a.idx = nil
|
a.idx = nil
|
||||||
|
a.images = make(map[string]*ebiten.Image)
|
||||||
a.maps = make(map[string]*Map)
|
a.maps = make(map[string]*Map)
|
||||||
a.menus = make(map[string]*Menu)
|
a.menus = make(map[string]*Menu)
|
||||||
a.objs = make(map[string]*Object)
|
a.objs = make(map[string]*Object)
|
||||||
|
42
internal/assetstore/image.go
Normal file
42
internal/assetstore/image.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package assetstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
_ "github.com/samuel/go-pcx/pcx" // PCX support
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *AssetStore) Image(name string) (*ebiten.Image, error) {
|
||||||
|
name = canonical(name)
|
||||||
|
if img, ok := a.images[name]; ok {
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// baps, ordoor, geas store .pcx files in Pic
|
||||||
|
// TODO: SL stores .bmp files in Res
|
||||||
|
filename, err := a.lookup(name, "pcx", "Pic")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
rawImg, _, err := image.Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := ebiten.NewImageFromImage(rawImg, ebiten.FilterDefault)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.images[name] = img
|
||||||
|
return img, nil
|
||||||
|
}
|
@@ -15,8 +15,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
expectedMagic = []byte("\x08\x00WHMAP\x00")
|
expectedMagic = []byte("\x15\x00AMB_MAP\x00")
|
||||||
expectedSetNameOffset = uint32(0x34)
|
expectedSetNameOffset = uint32(0x10)
|
||||||
notImplemented = fmt.Errorf("Not implemented")
|
notImplemented = fmt.Errorf("Not implemented")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,35 +25,25 @@ const (
|
|||||||
MaxLength = 100 // Y coordinate
|
MaxLength = 100 // Y coordinate
|
||||||
MaxWidth = 130 // X coordinate
|
MaxWidth = 130 // X coordinate
|
||||||
|
|
||||||
CellSize = 16 // seems to be
|
CellSize = 13 // seems to be
|
||||||
|
|
||||||
cellDataOffset = 0x110 // definitely
|
cellDataOffset = 0xc0
|
||||||
cellCount = MaxHeight * MaxLength * MaxWidth
|
cellCount = MaxHeight * MaxLength * MaxWidth
|
||||||
)
|
)
|
||||||
|
|
||||||
type Header struct {
|
type Header struct {
|
||||||
IsCampaignMap uint32 // Tentatively: 0 = no, 1 = yes
|
Magic [10]byte // "\x15\x00AMB_MAP\x00"
|
||||||
MinWidth uint32
|
|
||||||
MinLength uint32
|
|
||||||
MaxWidth uint32
|
|
||||||
MaxLength uint32
|
|
||||||
Unknown1 uint32
|
|
||||||
Unknown2 uint32
|
|
||||||
Unknown3 uint32
|
|
||||||
Unknown4 uint32
|
|
||||||
Magic [8]byte // "\x08\x00WHMAP\x00"
|
|
||||||
Unknown5 uint32
|
|
||||||
Unknown6 uint32
|
|
||||||
SetName [8]byte // Links to a filename in `/Sets/*.set`
|
SetName [8]byte // Links to a filename in `/Sets/*.set`
|
||||||
// Need to investigate the rest of the header too
|
// Need to investigate the rest of the header too
|
||||||
|
IsCampaignMap byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Header) Width() int {
|
func (h Header) Width() int {
|
||||||
return int(h.MaxWidth - h.MinWidth)
|
return MaxWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Header) Length() int {
|
func (h Header) Length() int {
|
||||||
return int(h.MaxLength - h.MinLength)
|
return MaxLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Header) Height() int {
|
func (h Header) Height() int {
|
||||||
@@ -80,7 +70,7 @@ type ObjRef struct {
|
|||||||
|
|
||||||
// The index into a set palette to retrieve the object
|
// The index into a set palette to retrieve the object
|
||||||
func (o ObjRef) Index() int {
|
func (o ObjRef) Index() int {
|
||||||
return int(o.AreaByte)
|
return int(o.AreaByte & 0x7f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o ObjRef) Sprite() int {
|
func (o ObjRef) Sprite() int {
|
||||||
@@ -91,12 +81,13 @@ func (o ObjRef) Sprite() int {
|
|||||||
// The top bit seems to say whether we should draw or not.
|
// The top bit seems to say whether we should draw or not.
|
||||||
func (o ObjRef) IsActive() bool {
|
func (o ObjRef) IsActive() bool {
|
||||||
return (o.SpriteAndFlagByte & 0x80) == 0x80
|
return (o.SpriteAndFlagByte & 0x80) == 0x80
|
||||||
}
|
} // PARIS is 78 x 60 x 7
|
||||||
|
// 4E 3C 7
|
||||||
|
/*
|
||||||
type Cell struct {
|
type Cell struct {
|
||||||
DoorAndCanisterRelated byte
|
DoorAndCanisterRelated byte
|
||||||
DoorLockAndReactorRelated byte
|
// DoorLockAndReactorRelated byte
|
||||||
Unknown2 byte
|
// Unknown2 byte
|
||||||
Surface ObjRef
|
Surface ObjRef
|
||||||
Left ObjRef
|
Left ObjRef
|
||||||
Right ObjRef
|
Right ObjRef
|
||||||
@@ -105,43 +96,60 @@ type Cell struct {
|
|||||||
Unknown12 byte
|
Unknown12 byte
|
||||||
Unknown13 byte
|
Unknown13 byte
|
||||||
Unknown14 byte
|
Unknown14 byte
|
||||||
SquadRelated byte
|
// SquadRelated byte
|
||||||
|
}*/
|
||||||
|
|
||||||
|
type Cell struct {
|
||||||
|
Unknown1 byte
|
||||||
|
Surface ObjRef
|
||||||
|
Left ObjRef
|
||||||
|
Right ObjRef
|
||||||
|
Center ObjRef
|
||||||
|
Unknown2 [4]byte
|
||||||
|
|
||||||
|
/*
|
||||||
|
DoorAndCanisterRelated byte
|
||||||
|
// DoorLockAndReactorRelated byte
|
||||||
|
// Unknown2 byte
|
||||||
|
Surface ObjRef
|
||||||
|
Left ObjRef
|
||||||
|
Right ObjRef
|
||||||
|
Center ObjRef
|
||||||
|
Unknown11 byte
|
||||||
|
Unknown12 byte
|
||||||
|
Unknown13 byte
|
||||||
|
Unknown14 byte
|
||||||
|
SquadRelated byte*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cell) At(n int) byte {
|
func (c *Cell) At(n int) byte {
|
||||||
switch n {
|
switch n {
|
||||||
case 0:
|
case 0:
|
||||||
return c.DoorAndCanisterRelated
|
return c.Unknown1
|
||||||
case 1:
|
case 1:
|
||||||
return c.DoorLockAndReactorRelated
|
|
||||||
case 2:
|
|
||||||
return c.Unknown2
|
|
||||||
case 3:
|
|
||||||
return c.Surface.AreaByte
|
return c.Surface.AreaByte
|
||||||
case 4:
|
case 2:
|
||||||
return c.Surface.SpriteAndFlagByte
|
return c.Surface.SpriteAndFlagByte
|
||||||
case 5:
|
case 3:
|
||||||
return c.Left.AreaByte
|
return c.Left.AreaByte
|
||||||
case 6:
|
case 4:
|
||||||
return c.Left.SpriteAndFlagByte
|
return c.Left.SpriteAndFlagByte
|
||||||
case 7:
|
case 5:
|
||||||
return c.Right.AreaByte
|
return c.Right.AreaByte
|
||||||
case 8:
|
case 6:
|
||||||
return c.Right.SpriteAndFlagByte
|
return c.Right.SpriteAndFlagByte
|
||||||
case 9:
|
case 7:
|
||||||
return c.Center.AreaByte
|
return c.Center.AreaByte
|
||||||
case 10:
|
case 8:
|
||||||
return c.Center.SpriteAndFlagByte
|
return c.Center.SpriteAndFlagByte
|
||||||
|
case 9:
|
||||||
|
return c.Unknown2[0]
|
||||||
|
case 10:
|
||||||
|
return c.Unknown2[1]
|
||||||
case 11:
|
case 11:
|
||||||
return c.Unknown11
|
return c.Unknown2[2]
|
||||||
case 12:
|
case 12:
|
||||||
return c.Unknown12
|
return c.Unknown2[3]
|
||||||
case 13:
|
|
||||||
return c.Unknown13
|
|
||||||
case 14:
|
|
||||||
return c.Unknown14
|
|
||||||
case 15:
|
|
||||||
return c.SquadRelated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -150,15 +158,23 @@ func (c *Cell) At(n int) byte {
|
|||||||
// Cells is always a fixed size; use At to get a cell according to x,y,z
|
// Cells is always a fixed size; use At to get a cell according to x,y,z
|
||||||
type Cells []Cell
|
type Cells []Cell
|
||||||
|
|
||||||
|
// 6 Possibilities for being laid out in memory. Most likely:
|
||||||
|
// XXYYZZ
|
||||||
|
// OR
|
||||||
|
// XYZXYZ
|
||||||
|
|
||||||
func (c Cells) At(x, y, z int) Cell {
|
func (c Cells) At(x, y, z int) Cell {
|
||||||
return c[(z*MaxLength*MaxWidth)+(y*MaxWidth)+x]
|
// log.Printf("At (%v,%v,%v)=%v", x, y, z, x*y*z)
|
||||||
|
return c[(z*MaxLength*MaxWidth)+
|
||||||
|
(y*MaxWidth)+
|
||||||
|
x]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Header) Check() []error {
|
func (h Header) Check() []error {
|
||||||
var out []error
|
var out []error
|
||||||
if h.IsCampaignMap > 1 {
|
// if h.IsCampaignMap > 1 {
|
||||||
out = append(out, fmt.Errorf("Expected 0 or 1 for IsCampaignMap, got %v", h.IsCampaignMap))
|
// out = append(out, fmt.Errorf("Expected 0 or 1 for IsCampaignMap, got %v", h.IsCampaignMap))
|
||||||
}
|
// }
|
||||||
|
|
||||||
if bytes.Compare(expectedMagic, h.Magic[:]) != 0 {
|
if bytes.Compare(expectedMagic, h.Magic[:]) != 0 {
|
||||||
out = append(out, fmt.Errorf("Unexpected magic value: %v", h.Magic))
|
out = append(out, fmt.Errorf("Unexpected magic value: %v", h.Magic))
|
||||||
@@ -176,10 +192,10 @@ type GameMap struct {
|
|||||||
|
|
||||||
func (m *GameMap) Rect() image.Rectangle {
|
func (m *GameMap) Rect() image.Rectangle {
|
||||||
return image.Rect(
|
return image.Rect(
|
||||||
int(m.Header.MinWidth),
|
int(0),
|
||||||
int(m.Header.MinLength),
|
int(0),
|
||||||
int(m.Header.MaxWidth),
|
int(m.Width()-1),
|
||||||
int(m.Header.MaxLength),
|
int(m.Length()-1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
internal/ordoor/display.go
Normal file
20
internal/ordoor/display.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package ordoor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o *Ordoor) DisplayImageFor(d time.Duration, name string) error {
|
||||||
|
img, err := o.assets.Image(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.pic = img
|
||||||
|
go func() {
|
||||||
|
<-time.After(d)
|
||||||
|
o.pic = nil // FIXME: this is a race condition and a half
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -7,6 +7,7 @@ package ordoor
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten"
|
||||||
"github.com/hajimehoshi/ebiten/audio"
|
"github.com/hajimehoshi/ebiten/audio"
|
||||||
@@ -29,6 +30,10 @@ type Ordoor struct {
|
|||||||
// Relevant to interface state
|
// Relevant to interface state
|
||||||
flow *flow.Flow
|
flow *flow.Flow
|
||||||
|
|
||||||
|
// FIXME: should be put inside flow
|
||||||
|
// If this is set, we display it instead of flow
|
||||||
|
pic *ebiten.Image
|
||||||
|
|
||||||
// Relevant to campaign state
|
// Relevant to campaign state
|
||||||
ship *ship.Ship
|
ship *ship.Ship
|
||||||
}
|
}
|
||||||
@@ -81,10 +86,6 @@ func Run(configFile string, overrideX, overrideY int) error {
|
|||||||
|
|
||||||
ordoor.win = win
|
ordoor.win = win
|
||||||
|
|
||||||
if err := ordoor.setupFlow(); err != nil {
|
|
||||||
return fmt.Errorf("failed to setup UI flow: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ordoor.Run(); err != nil {
|
if err := ordoor.Run(); err != nil {
|
||||||
return fmt.Errorf("Run finished with error: %v", err)
|
return fmt.Errorf("Run finished with error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -93,12 +94,16 @@ func Run(configFile string, overrideX, overrideY int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Ordoor) Run() error {
|
func (o *Ordoor) Run() error {
|
||||||
// FIXME: we're missing a screen about SSI here
|
// FIXME: these should be displayed *after*, not *before*, the copyright
|
||||||
if o.config.Options.PlayMovies {
|
if o.config.Options.PlayMovies {
|
||||||
o.PlaySkippableVideo("LOGOS")
|
o.PlaySkippableVideo("LOGOS")
|
||||||
o.PlaySkippableVideo("movie1")
|
o.PlaySkippableVideo("movie1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := o.DisplayImageFor(time.Second, "copyright"); err != nil {
|
||||||
|
log.Printf("Failed to display copyright image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
err := o.win.Run()
|
err := o.win.Run()
|
||||||
if err == flow.ErrExit {
|
if err == flow.ErrExit {
|
||||||
log.Printf("Exit requested")
|
log.Printf("Exit requested")
|
||||||
@@ -151,6 +156,16 @@ func (o *Ordoor) setupFlow() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *Ordoor) Update(screenX, screenY int) error {
|
func (o *Ordoor) Update(screenX, screenY int) error {
|
||||||
|
if pic := o.pic; pic != nil {
|
||||||
|
return nil // Ignore flow until we don't have a pic any more
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.flow == nil {
|
||||||
|
if err := o.setupFlow(); err != nil {
|
||||||
|
return fmt.Errorf("failed to setup UI flow: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure music is doing the right thing
|
// Ensure music is doing the right thing
|
||||||
if o.music != nil && o.music.IsPlaying() != o.config.Options.PlayMusic {
|
if o.music != nil && o.music.IsPlaying() != o.config.Options.PlayMusic {
|
||||||
if o.config.Options.PlayMusic {
|
if o.config.Options.PlayMusic {
|
||||||
@@ -165,9 +180,24 @@ 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 {
|
||||||
|
// Scale the picture to the screen and draw it
|
||||||
|
scaleX := float64(screen.Bounds().Dx()) / float64(pic.Bounds().Dx())
|
||||||
|
scaleY := float64(screen.Bounds().Dy()) / float64(pic.Bounds().Dy())
|
||||||
|
|
||||||
|
do := &ebiten.DrawImageOptions{}
|
||||||
|
do.GeoM.Scale(scaleX, scaleY)
|
||||||
|
|
||||||
|
return screen.DrawImage(pic, do)
|
||||||
|
}
|
||||||
|
|
||||||
return o.flow.Draw(screen)
|
return o.flow.Draw(screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Ordoor) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
|
func (o *Ordoor) Cursor() (*ebiten.Image, *ebiten.DrawImageOptions, error) {
|
||||||
|
if o.flow != nil {
|
||||||
return o.flow.Cursor()
|
return o.flow.Cursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user