This makes menus display more correctly, and also fixes trees and other objects on the main map, although it messes up bounds clipping (sigh).
222 lines
4.8 KiB
Go
222 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"image"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/hajimehoshi/ebiten"
|
|
|
|
"code.ur.gs/lupine/ordoor/internal/conv"
|
|
"code.ur.gs/lupine/ordoor/internal/data"
|
|
"code.ur.gs/lupine/ordoor/internal/fonts"
|
|
"code.ur.gs/lupine/ordoor/internal/menus"
|
|
"code.ur.gs/lupine/ordoor/internal/ui"
|
|
)
|
|
|
|
var (
|
|
gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation")
|
|
menuFile = flag.String("menu", "", "Path to a .mnu file, e.g. ./orig/Menu/MainGame.mnu")
|
|
)
|
|
|
|
type env struct {
|
|
menu *menus.Menu
|
|
objects []*conv.Object
|
|
|
|
fonts []*conv.Font
|
|
fontObjs []*conv.Object
|
|
|
|
step int
|
|
state state
|
|
lastState state
|
|
}
|
|
|
|
type state struct {
|
|
// Redraw the window if these change
|
|
winBounds image.Rectangle
|
|
}
|
|
|
|
func loadObjects(names ...string) ([]*conv.Object, error) {
|
|
objs := make([]*conv.Object, 0, len(names))
|
|
|
|
for _, name := range names {
|
|
objFile := filepath.Join(filepath.Dir(*menuFile), name)
|
|
rawObj, err := data.LoadObject(objFile)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load %s: %v", name, err)
|
|
}
|
|
|
|
obj, err := conv.ConvertObject(rawObj, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
objs = append(objs, obj)
|
|
}
|
|
|
|
return objs, nil
|
|
}
|
|
|
|
func loadFonts(names ...string) ([]*conv.Font, error) {
|
|
var out []*conv.Font
|
|
for _, name := range names {
|
|
fnt, err := fonts.LoadFont(filepath.Join(*gamePath, "Fonts", name+".fnt"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%v: %v", name, err)
|
|
}
|
|
|
|
out = append(out, conv.ConvertFont(fnt))
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if *gamePath == "" || *menuFile == "" {
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
menu, err := menus.LoadMenu(*menuFile)
|
|
if err != nil {
|
|
log.Fatalf("Couldn't load menu file %s: %v", *menuFile, err)
|
|
}
|
|
|
|
if i18n, err := data.LoadI18n(filepath.Join(*gamePath, "Data", data.I18nFile)); err != nil {
|
|
log.Printf("Failed to load i18n data, skipping internationalization: %v", err)
|
|
} else {
|
|
menu.Internationalize(i18n)
|
|
}
|
|
|
|
loadedFonts, err := loadFonts(menu.FontNames...)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load font: %v", err)
|
|
}
|
|
|
|
menuObjs, err := loadObjects(menu.ObjectFiles...)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load objects: %v", err)
|
|
}
|
|
|
|
state := state{}
|
|
env := &env{
|
|
menu: menu,
|
|
objects: menuObjs,
|
|
fonts: loadedFonts,
|
|
state: state,
|
|
lastState: state,
|
|
}
|
|
|
|
win, err := ui.NewWindow("View Menu: " + *menuFile)
|
|
if err != nil {
|
|
log.Fatal("Couldn't create window: %v", err)
|
|
}
|
|
|
|
if err := win.Run(env.Update, env.Draw); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (e *env) Update() error {
|
|
// No behaviour yet
|
|
|
|
e.step += 1
|
|
e.lastState = e.state
|
|
return nil
|
|
}
|
|
|
|
const (
|
|
origX = 640.0
|
|
origY = 480.0
|
|
)
|
|
|
|
func (e *env) Draw(screen *ebiten.Image) error {
|
|
// The menus expect to be drawn to a 640x480 screen. We need to scale and
|
|
// project that so it fills the window appropriately. This is a combination
|
|
// of translate + zoom
|
|
winSize := screen.Bounds().Max
|
|
scaleX := float64(winSize.X) / float64(origX)
|
|
scaleY := float64(winSize.Y) / float64(origY)
|
|
|
|
cam := ebiten.GeoM{}
|
|
cam.Scale(scaleX, scaleY)
|
|
|
|
for _, record := range e.menu.Records {
|
|
if err := e.drawRecord(record, screen, cam); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *env) drawRecord(record *menus.Record, screen *ebiten.Image, offset ebiten.GeoM) error {
|
|
origOffset := offset
|
|
|
|
// Draw this record if it's valid to do so. FIXME: lots to learn
|
|
if len(record.SpriteId) >= 0 {
|
|
spriteId := record.SpriteId[0]
|
|
x := float64(record.X)
|
|
y := float64(record.Y)
|
|
|
|
// Maybe: we either give spriteid, or y,x,spriteId ? Unsure, doesn't seem
|
|
// to be needed for now
|
|
if len(record.SpriteId) == 3 {
|
|
// x = x + float64(record.SpriteId[1])
|
|
// y = y + float64(record.SpriteId[0])
|
|
spriteId = record.SpriteId[2]
|
|
}
|
|
|
|
// FIXME: some here are set at -1. Presume that means don't draw.
|
|
if spriteId < 0 {
|
|
goto out
|
|
}
|
|
|
|
// FIXME: some are set at -1, -1. No idea why. Origin?
|
|
if x < 0.0 {
|
|
x = 0.0
|
|
}
|
|
if y < 0.0 {
|
|
y = 0.0
|
|
}
|
|
|
|
// FIXME: Need to handle multiple objects
|
|
obj := e.objects[0]
|
|
sprite := obj.Sprites[spriteId]
|
|
|
|
|
|
x = x + float64(sprite.XOffset)
|
|
y = y + float64(sprite.YOffset)
|
|
// Account for scaling
|
|
x, y = offset.Apply(x, y)
|
|
|
|
log.Printf(
|
|
"Drawing id=%v type=%v spriteid=%v x=%v(+%v) y=%v(%+v) desc=%q parent=%p",
|
|
record.Id, record.Type, spriteId, record.X, record.Y, sprite.XOffset, sprite.YOffset, record.Desc, record.Parent,
|
|
)
|
|
|
|
offset.Translate(x, y)
|
|
screen.DrawImage(sprite.Image, &ebiten.DrawImageOptions{GeoM: offset})
|
|
|
|
// FIXME: we probably shouldn't draw everything?
|
|
// FIXME: handle multiple fonts
|
|
if len(e.fonts) > 0 && record.Desc != "" {
|
|
e.fonts[0].Output(screen, origOffset, record.Desc)
|
|
}
|
|
}
|
|
out:
|
|
// Draw all children of this record
|
|
for _, child := range record.Children {
|
|
if err := e.drawRecord(child, screen, offset); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|