Build a simple animation viewer
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@
|
|||||||
/loader
|
/loader
|
||||||
/orig
|
/orig
|
||||||
/palette-idx
|
/palette-idx
|
||||||
|
/view-ani
|
||||||
/view-font
|
/view-font
|
||||||
/view-obj
|
/view-obj
|
||||||
/view-map
|
/view-map
|
||||||
|
7
Makefile
7
Makefile
@@ -2,7 +2,7 @@ srcfiles = Makefile go.mod $(shell find . -iname *.go)
|
|||||||
|
|
||||||
GOBUILD ?= go build -tags ebitengl
|
GOBUILD ?= go build -tags ebitengl
|
||||||
|
|
||||||
all: loader ordoor palette-idx view-font view-obj view-map view-menu view-minimap view-set
|
all: loader ordoor palette-idx view-ani view-font view-obj view-map view-menu view-minimap view-set
|
||||||
|
|
||||||
loader: $(srcfiles)
|
loader: $(srcfiles)
|
||||||
$(GOBUILD) -o loader ./cmd/loader
|
$(GOBUILD) -o loader ./cmd/loader
|
||||||
@@ -10,6 +10,9 @@ loader: $(srcfiles)
|
|||||||
palette-idx: $(srcfiles)
|
palette-idx: $(srcfiles)
|
||||||
$(GOBUILD) -o palette-idx ./cmd/palette-idx
|
$(GOBUILD) -o palette-idx ./cmd/palette-idx
|
||||||
|
|
||||||
|
view-ani: $(srcfiles)
|
||||||
|
$(GOBUILD) -o view-ani ./cmd/view-ani
|
||||||
|
|
||||||
view-font: $(srcfiles)
|
view-font: $(srcfiles)
|
||||||
$(GOBUILD) -o view-font ./cmd/view-font
|
$(GOBUILD) -o view-font ./cmd/view-font
|
||||||
|
|
||||||
@@ -32,6 +35,6 @@ ordoor: $(srcfiles)
|
|||||||
$(GOBUILD) -o ordoor ./cmd/ordoor
|
$(GOBUILD) -o ordoor ./cmd/ordoor
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f loader ordoor view-obj view-map view-minimap view-set palette-idx view-font
|
rm -f loader ordoor view-ani view-obj view-map view-minimap view-set palette-idx view-font
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
164
cmd/view-ani/main.go
Normal file
164
cmd/view-ani/main.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"image"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten"
|
||||||
|
|
||||||
|
"code.ur.gs/lupine/ordoor/internal/assetstore"
|
||||||
|
"code.ur.gs/lupine/ordoor/internal/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation")
|
||||||
|
groupIdx = flag.Int("group", 0, "Group index to start at")
|
||||||
|
recIdx = flag.Int("record", 0, "Record index to start at")
|
||||||
|
|
||||||
|
winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension")
|
||||||
|
winY = flag.Int("win-y", 1024, "Pre-scaled window Y dimension")
|
||||||
|
)
|
||||||
|
|
||||||
|
type env struct {
|
||||||
|
assets *assetstore.AssetStore
|
||||||
|
ani *assetstore.Animation
|
||||||
|
step int
|
||||||
|
|
||||||
|
state state
|
||||||
|
lastState state
|
||||||
|
}
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
groupIdx int
|
||||||
|
recIdx int
|
||||||
|
|
||||||
|
zoom float64
|
||||||
|
origin image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *gamePath == "" {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
assets, err := assetstore.New(*gamePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to set up asset store: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state := state{
|
||||||
|
zoom: 2.0,
|
||||||
|
origin: image.Point{0, 0},
|
||||||
|
groupIdx: *groupIdx,
|
||||||
|
recIdx: *recIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
env := &env{
|
||||||
|
assets: assets,
|
||||||
|
state: state,
|
||||||
|
lastState: state,
|
||||||
|
}
|
||||||
|
|
||||||
|
win, err := ui.NewWindow(env, "View Animations", *winX, *winY)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
win.OnKeyUp(ebiten.KeyMinus, env.changeGroup(-1))
|
||||||
|
win.OnKeyUp(ebiten.KeyEqual, env.changeGroup(+1))
|
||||||
|
|
||||||
|
win.OnKeyUp(ebiten.KeyComma, env.changeRec(-1))
|
||||||
|
win.OnKeyUp(ebiten.KeyPeriod, env.changeRec(+1))
|
||||||
|
|
||||||
|
win.OnKeyUp(ebiten.KeyLeft, env.changeOrigin(-16, +0))
|
||||||
|
win.OnKeyUp(ebiten.KeyRight, env.changeOrigin(+16, +0))
|
||||||
|
win.OnKeyUp(ebiten.KeyUp, env.changeOrigin(+0, -16))
|
||||||
|
win.OnKeyUp(ebiten.KeyDown, env.changeOrigin(+0, +16))
|
||||||
|
win.OnMouseWheel(env.changeZoom)
|
||||||
|
|
||||||
|
// The main thread now belongs to ebiten
|
||||||
|
if err := win.Run(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *env) Update(screenX, screenY int) error {
|
||||||
|
if e.step == 0 || e.lastState != e.state {
|
||||||
|
|
||||||
|
ani, err := e.assets.Animation(e.state.groupIdx, e.state.recIdx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.ani = ani
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"new state: group=%d rec=%d zoom=%.2f, origin=%+v",
|
||||||
|
e.state.groupIdx,
|
||||||
|
e.state.recIdx,
|
||||||
|
e.state.zoom,
|
||||||
|
e.state.origin,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be the final action
|
||||||
|
e.step += 1
|
||||||
|
e.lastState = e.state
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *env) Draw(screen *ebiten.Image) error {
|
||||||
|
cam := ebiten.GeoM{}
|
||||||
|
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
|
||||||
|
|
||||||
|
if len(e.ani.Frames) > 0 {
|
||||||
|
sprite := e.ani.Frames[e.step/4%len(e.ani.Frames)]
|
||||||
|
|
||||||
|
return screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: cam})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *env) changeGroup(by int) func() {
|
||||||
|
return func() {
|
||||||
|
e.state.groupIdx += by
|
||||||
|
|
||||||
|
if e.state.groupIdx < 0 {
|
||||||
|
e.state.groupIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: upper bounds checking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *env) changeRec(by int) func() {
|
||||||
|
return func() {
|
||||||
|
e.state.recIdx += by
|
||||||
|
|
||||||
|
if e.state.recIdx < 0 {
|
||||||
|
e.state.recIdx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: upper bounds checking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *env) changeOrigin(byX, byY int) func() {
|
||||||
|
return func() {
|
||||||
|
e.state.origin.X += byX
|
||||||
|
e.state.origin.Y += byY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *env) changeZoom(_, y float64) {
|
||||||
|
// Zoom in and out with the mouse wheel
|
||||||
|
e.state.zoom *= math.Pow(1.2, y)
|
||||||
|
}
|
78
internal/assetstore/ani.go
Normal file
78
internal/assetstore/ani.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package assetstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.ur.gs/lupine/ordoor/internal/idx"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Animation struct {
|
||||||
|
Frames []*Sprite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AssetStore) AnimationsIndex() (*idx.Idx, error) {
|
||||||
|
if a.idx != nil {
|
||||||
|
return a.idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, err := a.lookup("WarHammer", "idx", "Idx")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, err := idx.Load(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.idx = idx
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AssetStore) AnimationsObject() (*Object, error) {
|
||||||
|
if a.aniObj != nil {
|
||||||
|
return a.aniObj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filename, err := a.lookup("WarHammer", "ani", "Anim")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := a.ObjectByPath(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.aniObj = obj
|
||||||
|
return obj, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AssetStore) Animation(groupIdx, recIdx int) (*Animation, error) {
|
||||||
|
idx, err := a.AnimationsIndex()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := a.AnimationsObject()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group := idx.Groups[groupIdx]
|
||||||
|
if group.Spec.Count == 0 {
|
||||||
|
return &Animation{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rec := group.Records[recIdx]
|
||||||
|
det := group.Details[recIdx]
|
||||||
|
|
||||||
|
first := int(group.Spec.SpriteIdx) + int(det.FirstSprite)
|
||||||
|
last := int(group.Spec.SpriteIdx) + int(det.LastSprite)
|
||||||
|
count := last - first + 1
|
||||||
|
|
||||||
|
sprites, err := obj.Sprites(first, count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Animation{Frames: sprites}, nil
|
||||||
|
}
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.ur.gs/lupine/ordoor/internal/data"
|
"code.ur.gs/lupine/ordoor/internal/data"
|
||||||
|
"code.ur.gs/lupine/ordoor/internal/idx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -34,17 +35,18 @@ type AssetStore struct {
|
|||||||
entries entryMap
|
entries entryMap
|
||||||
|
|
||||||
// These members are used to store things we've already loaded
|
// These members are used to store things we've already loaded
|
||||||
|
aniObj *Object
|
||||||
cursorObj *Object
|
cursorObj *Object
|
||||||
|
cursors map[CursorName]*Cursor
|
||||||
cursors map[CursorName]*Cursor
|
fonts map[string]*Font
|
||||||
fonts map[string]*Font
|
generic *data.Generic
|
||||||
generic *data.Generic
|
idx *idx.Idx
|
||||||
maps map[string]*Map
|
maps map[string]*Map
|
||||||
menus map[string]*Menu
|
menus map[string]*Menu
|
||||||
objs map[string]*Object
|
objs map[string]*Object
|
||||||
sets map[string]*Set
|
sets map[string]*Set
|
||||||
sounds map[string]*Sound
|
sounds map[string]*Sound
|
||||||
strings *data.I18n
|
strings *data.I18n
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new AssetStore
|
// New returns a new AssetStore
|
||||||
@@ -88,10 +90,12 @@ func (a *AssetStore) Refresh() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Refresh
|
// Refresh
|
||||||
|
a.aniObj = nil
|
||||||
a.cursorObj = nil
|
a.cursorObj = nil
|
||||||
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.idx = nil
|
||||||
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)
|
||||||
|
@@ -41,18 +41,7 @@ func (m *Menu) Images(objIdx, start, count int) ([]*ebiten.Image, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) Sprites(objIdx, start, count int) ([]*Sprite, error) {
|
func (m *Menu) Sprites(objIdx, start, count int) ([]*Sprite, error) {
|
||||||
out := make([]*Sprite, count)
|
return m.objects[objIdx].Sprites(start, count)
|
||||||
|
|
||||||
for i := start; i < start+count; i++ {
|
|
||||||
sprite, err := m.Sprite(objIdx, i)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out[i-start] = sprite
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Menu) Sprite(objIdx, idx int) (*Sprite, error) {
|
func (m *Menu) Sprite(objIdx, idx int) (*Sprite, error) {
|
||||||
|
@@ -90,6 +90,21 @@ func (o *Object) LoadSprites() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Object) Sprites(start, count int) ([]*Sprite, error) {
|
||||||
|
out := make([]*Sprite, count)
|
||||||
|
|
||||||
|
for i := start; i < start+count; i++ {
|
||||||
|
sprite, err := o.Sprite(i)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out[i-start] = sprite
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Object) Sprite(idx int) (*Sprite, error) {
|
func (o *Object) Sprite(idx int) (*Sprite, error) {
|
||||||
if sprite := o.sprites[idx]; sprite != nil {
|
if sprite := o.sprites[idx]; sprite != nil {
|
||||||
return sprite, nil
|
return sprite, nil
|
||||||
|
Reference in New Issue
Block a user