From c1268e8d57b895597742125463803667d5fa5050 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 1 Jun 2020 01:08:53 +0100 Subject: [PATCH] Start reorganising for multiple games --- .gitignore | 18 +++---- Makefile | 45 +++++++++-------- README.md | 82 +++++++++++++++++-------------- cmd/loader/main.go | 57 +++++++++++---------- cmd/view-ani/main.go | 14 ++++-- cmd/view-font/main.go | 14 ++++-- cmd/view-map/main.go | 18 +++++-- cmd/view-menu/main.go | 14 ++++-- cmd/view-obj/main.go | 20 +++++--- cmd/view-set/main.go | 16 ++++-- config.toml.example | 21 ++++++-- internal/assetstore/assetstore.go | 23 +++++---- internal/config/config.go | 54 +++++++++++++++----- internal/ordoor/ordoor.go | 4 +- internal/ordoor/videos.go | 2 +- 15 files changed, 257 insertions(+), 145 deletions(-) diff --git a/.gitignore b/.gitignore index 656ed88..888dd2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,8 @@ /config.toml /loader -/orig -/palette-idx -/view-ani -/view-font -/view-obj -/view-map -/view-minimap -/view-menu -/view-set -/ordoor -/investigation/Maps -/investigation/Obj +/isos +/CG +/SL +/SaW +/WoW +/bin diff --git a/Makefile b/Makefile index 284ce27..8399594 100644 --- a/Makefile +++ b/Makefile @@ -4,37 +4,40 @@ GOBUILD ?= go build -tags ebitengl all: loader ordoor palette-idx view-ani view-font view-obj view-map view-menu view-minimap view-set -loader: $(srcfiles) - $(GOBUILD) -o loader ./cmd/loader +bin: + mkdir bin -palette-idx: $(srcfiles) - $(GOBUILD) -o palette-idx ./cmd/palette-idx +loader: bin $(srcfiles) + $(GOBUILD) -o bin/loader ./cmd/loader -view-ani: $(srcfiles) - $(GOBUILD) -o view-ani ./cmd/view-ani +palette-idx: bin $(srcfiles) + $(GOBUILD) -o bin/palette-idx ./cmd/palette-idx -view-font: $(srcfiles) - $(GOBUILD) -o view-font ./cmd/view-font +view-ani: bin $(srcfiles) + $(GOBUILD) -o bin/view-ani ./cmd/view-ani -view-obj: $(srcfiles) - $(GOBUILD) -o view-obj ./cmd/view-obj +view-font: bin $(srcfiles) + $(GOBUILD) -o bin/view-font ./cmd/view-font -view-map: $(srcfiles) - $(GOBUILD) -o view-map ./cmd/view-map +view-obj: bin $(srcfiles) + $(GOBUILD) -o bin/view-obj ./cmd/view-obj -view-menu: $(srcfiles) - $(GOBUILD) -o view-menu ./cmd/view-menu +view-map: bin $(srcfiles) + $(GOBUILD) -o bin/view-map ./cmd/view-map -view-minimap: $(srcfiles) - $(GOBUILD) -o view-minimap ./cmd/view-minimap +view-menu: bin $(srcfiles) + $(GOBUILD) -o bin/view-menu ./cmd/view-menu -view-set: $(srcfiles) - $(GOBUILD) -o view-set ./cmd/view-set +view-minimap: bin $(srcfiles) + $(GOBUILD) -o bin/view-minimap ./cmd/view-minimap -ordoor: $(srcfiles) - $(GOBUILD) -o ordoor ./cmd/ordoor +view-set: bin $(srcfiles) + $(GOBUILD) -o bin/view-set ./cmd/view-set + +ordoor: bin $(srcfiles) + $(GOBUILD) -o bin/ordoor ./cmd/ordoor clean: - rm -f loader ordoor view-ani view-obj view-map view-minimap view-set palette-idx view-font + rm -rf bin .PHONY: all clean diff --git a/README.md b/README.md index e4b8239..c939adc 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,34 @@ # Ordoor Ordoor is an **unofficial** [game engine recreation](https://en.wikipedia.org/wiki/Game_engine_recreation) -of the classic game from 1998, [Warhammer 40,000: Chaos Gate](https://en.wikipedia.org/wiki/Warhammer_40,000:_Chaos_Gate) +of the Random Games, Inc., [Strategy Engine](https://www.mobygames.com/game-group/game-engine-random-games-1996-2000-strategy-engine), +which was in use from 1996 - 2000. -**You must have a copy of the original game data to use this project**. GOG is -the current publisher of this game; [you can purchase it here](https://www.gog.com/game/warhammer_40000_chaos_gate). +Four games are known to have been published for this engine: -"Warhammer 40,000" is a trademark of Games Workshop, and the game data used by -Ordoor contains Games Workshop intellectual property. I am confident that this -project uses all those things in accordance with the -[Intellectual Property Policy](https://www.games-workshop.com/en-GB/Intellectual-Property-Policy) -and the license granted when purchasing a copy of the game in question. Do let -me know if you see or suspect any violation, and I'll address it immediately. +* [Wages of War: The Business of Battle](https://en.wikipedia.org/wiki/Wages_of_War) (1996) +* [Soldiers At War](https://en.wikipedia.org/wiki/Soldiers_at_War) (1998) +* [Warhammer 40,000: Chaos Gate](https://en.wikipedia.org/wiki/Warhammer_40,000:_Chaos_Gate) (1998) [GOG](https://www.gog.com/game/warhammer_40000_chaos_gate) +* [Avalon Hill's Squad Leader](https://en.wikipedia.org/wiki/Avalon_Hill%27s_Squad_Leader) (2000) + +The aim of Ordoor is to be a complete reimplementation that allows all four +of these games to be played on modern hardware. It should also permit new games +of the same style to be built. + +For each of the games above, **You must have a copy of the original game data to play**. +Links are provided above if we're aware of an active publisher; otherwise, check +your back catalogue, or perhaps a local charity shop. + +Trademarks and intellectual property are the property of their respective +owners, and the games mentioned above (including the game data) are protected by +copyright. As a mere game engine recreation, we're confident that this project +operates legally, and that its goal is a noble one. Do get in touch if you +believe otherwise! Ordoor is a portmanteau of Order Door, which is, of course, the opposite of a -Chaos Gate. +Chaos Gate. The project began with a Chaos Gate recreation, then more games were +discovered, so scope expanded. A rename and/or rewrite may be on the cards as a +result. ## Current status @@ -31,6 +45,27 @@ I've just been informed that another game from 1998, [Soldiers At War](https://e seems to use the same engine. Maybe at some point Ordoor will be able to play both. Will that need a rename? Hmm. Watch this space. +## Long-term goals + +Once full playthrough of the official single-player campaign for all four games +has been achieved, thoughts turn to other things we could do. Here are some +ideas, mostly at random. + +Multi-player support. + +Graphics enhancements - 3D models instead of sprites, high-resolution tile sets, +32-bit colour, etc. Hopefully we'd be able to drop these in one at a time. + +Vastly improved AI. + +Mash-ups? How do mercenaries fare against cultists fare against Nazis? Only one +way to find out! + +New campaigns with existing assets. Tell new stories, or elaborate on / modify +existing ones. + +Completely new fantasy game using the same engine. + ## Building from source I'm writing code in Go at the moment, so you'll need to have a Go runtime @@ -119,30 +154,3 @@ $ ./scripts/convert-wav ./orig/Wav As with video playback, the ambition is to *eventually* remove this dependency and operate on the unmodified files instead. -## Miscellany - -"Mission Setup" includes information about available squad types - -From EquipDef.cpp Dumo: CEquipment we learn the following object types: - -0. DELETED -1. WEAPON -2. GRENADE -3. MEDIPACK -4. SCANNER -5. GENESEED -6. CLIP -7. DOOR KEY -8. DOOR KEY -9. DOOR KEY -10. DOOR KEY - -And we learn they can be "on".... - -0. CHARACTER -1. VEHICLE -2. CANISTER - -I'm starting to see some parallels with [this](https://github.com/shlainn/game-file-formats/wiki/) -in the data formats, and the timeline (1997) seems about right. Worth keeping an -eye on! diff --git a/cmd/loader/main.go b/cmd/loader/main.go index a9d8ac0..14058bd 100644 --- a/cmd/loader/main.go +++ b/cmd/loader/main.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/fonts" "code.ur.gs/lupine/ordoor/internal/idx" @@ -16,29 +17,38 @@ import ( ) var ( - gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") - skipObj = flag.Bool("skip-obj", true, "Skip loading .obj files") + configFile = flag.String("config", "config.toml", "Config file") + engine = flag.String("engine", "", "Override engine to use") + + skipObj = flag.Bool("skip-obj", true, "Skip loading .obj files") ) +// FIXME: all these paths are hardcoded with Chaos Gate in mind func main() { flag.Parse() - loadData() + cfg, err := config.Load(*configFile, *engine) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + engine := cfg.DefaultEngine() + gamePath := engine.DataDir + + loadData(filepath.Join(gamePath, "Data")) if !*skipObj { - loadObj() + loadObj(filepath.Join(gamePath, "Obj")) } - loadMapsFrom("Maps") - loadMapsFrom("MultiMaps") - loadSets() - loadMenus() - loadFonts() - loadIdx() + loadMapsFrom(filepath.Join(gamePath, "Maps")) + loadMapsFrom(filepath.Join(gamePath, "MultiMaps")) + loadSets(filepath.Join(gamePath, "Sets")) + loadMenus(filepath.Join(gamePath, "Menu")) + loadFonts(filepath.Join(gamePath, "Fonts")) + loadIdx(filepath.Join(gamePath, "Idx", "WarHammer.idx")) } -func loadData() { - dataPath := filepath.Join(*gamePath, "Data") +func loadData(dataPath string) { accountingPath := filepath.Join(dataPath, "Accounting.dat") aniObDefPath := filepath.Join(dataPath, "AniObDef.dat") genericDataPath := filepath.Join(dataPath, "GenericData.dat") @@ -84,9 +94,7 @@ func loadData() { ha.Print() } -func loadObj() { - objDataPath := filepath.Join(*gamePath, "Obj") - +func loadObj(objDataPath string) { // TODO: Obj/cpiece.rec isn't loaded by this. Do we need it? How do we know? log.Printf("Loading %s...", objDataPath) objects, err := data.LoadObjects(objDataPath) @@ -116,8 +124,7 @@ func loadObj() { } } -func loadMapsFrom(part string) { - mapsPath := filepath.Join(*gamePath, part) +func loadMapsFrom(mapsPath string) { log.Printf("Loading maps from %s", mapsPath) gameMaps, err := maps.LoadGameMaps(mapsPath) @@ -140,8 +147,7 @@ func loadMapsFrom(part string) { } } -func loadSets() { - setsPath := filepath.Join(*gamePath, "Sets") +func loadSets(setsPath string) { log.Printf("Loading sets from %s", setsPath) mapSets, err := sets.LoadSets(setsPath) @@ -157,8 +163,8 @@ func loadSets() { } } -func loadMenus() { - menusPath := filepath.Join(*gamePath, "Menu") +func loadMenus(menusPath string) { + log.Printf("Loading menus from %s", menusPath) menus, err := menus.LoadMenus(menusPath) if err != nil { @@ -187,8 +193,8 @@ func displayRecord(record *menus.Record, depth int) { fmt.Printf("%s* %s\n", strings.Repeat(" ", depth), content) } -func loadFonts() { - fontsPath := filepath.Join(*gamePath, "Fonts") +func loadFonts(fontsPath string) { + log.Printf("Loading fonts from %s", fontsPath) fonts, err := fonts.LoadFonts(fontsPath) if err != nil { @@ -200,8 +206,9 @@ func loadFonts() { } } -func loadIdx() { - idxPath := filepath.Join(*gamePath, "Idx", "WarHammer.idx") +func loadIdx(idxPath string) { + log.Printf("Loading idx from %s", idxPath) + idx, err := idx.Load(idxPath) if err != nil { log.Fatalf("Failed to parse %s as idx: %v", idxPath, err) diff --git a/cmd/view-ani/main.go b/cmd/view-ani/main.go index 1e37ec1..6ed3c1a 100644 --- a/cmd/view-ani/main.go +++ b/cmd/view-ani/main.go @@ -11,11 +11,14 @@ import ( "golang.org/x/image/colornames" "code.ur.gs/lupine/ordoor/internal/assetstore" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/ui" ) var ( - gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") + configFile = flag.String("config", "config.toml", "Config file") + engine = flag.String("engine", "", "Override engine to use") + groupIdx = flag.Int("group", 1, "Group index to start at") recIdx = flag.Int("record", 0, "Record index to start at") @@ -43,12 +46,17 @@ type state struct { func main() { flag.Parse() - if *gamePath == "" { + if *configFile == "" { flag.Usage() os.Exit(1) } - assets, err := assetstore.New(*gamePath) + cfg, err := config.Load(*configFile, *engine) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + assets, err := assetstore.New(cfg.DefaultEngine()) if err != nil { log.Fatal("Failed to set up asset store: %v", err) } diff --git a/cmd/view-font/main.go b/cmd/view-font/main.go index 2be58ae..60f489e 100644 --- a/cmd/view-font/main.go +++ b/cmd/view-font/main.go @@ -10,11 +10,14 @@ import ( "github.com/hajimehoshi/ebiten" "code.ur.gs/lupine/ordoor/internal/assetstore" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/ui" ) var ( - gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") + configFile = flag.String("config", "config.toml", "Config file") + engine = flag.String("engine", "", "Override engine to use") + fontName = flag.String("font", "", "Name of a font, e.g., basfont12") txt = flag.String("text", "Test string", "Text to render") @@ -37,12 +40,17 @@ type state struct { func main() { flag.Parse() - if *gamePath == "" || *fontName == "" { + if *configFile == "" || *fontName == "" { flag.Usage() os.Exit(1) } - assets, err := assetstore.New(*gamePath) + cfg, err := config.Load(*configFile, *engine) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + assets, err := assetstore.New(cfg.DefaultEngine()) if err != nil { log.Fatal(err) } diff --git a/cmd/view-map/main.go b/cmd/view-map/main.go index 48161c8..5f40c00 100644 --- a/cmd/view-map/main.go +++ b/cmd/view-map/main.go @@ -9,13 +9,16 @@ import ( "github.com/hajimehoshi/ebiten" "code.ur.gs/lupine/ordoor/internal/assetstore" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/scenario" "code.ur.gs/lupine/ordoor/internal/ui" ) var ( - gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") - gameMap = flag.String("map", "", "Name of a map, e.g., Chapter01") + configFile = flag.String("config", "config.toml", "Config file") + engine = flag.String("engine", "", "Override engine to use") + + gameMap = flag.String("map", "", "Name of a map, e.g., Chapter01") winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension") winY = flag.Int("win-y", 1024, "Pre-scaled window Y dimension") @@ -28,14 +31,19 @@ type env struct { func main() { flag.Parse() - if *gamePath == "" || *gameMap == "" { + if *configFile == "" || *gameMap == "" { flag.Usage() os.Exit(1) } - assets, err := assetstore.New(*gamePath) + cfg, err := config.Load(*configFile, *engine) if err != nil { - log.Fatalf("Failed to scan root directory %v: %v", *gamePath, err) + log.Fatalf("Failed to load config: %v", err) + } + + assets, err := assetstore.New(cfg.DefaultEngine()) + if err != nil { + log.Fatalf("Failed to scan root directory: %v", err) } scenario, err := scenario.NewScenario(assets, *gameMap) diff --git a/cmd/view-menu/main.go b/cmd/view-menu/main.go index f4d7be8..e3e51ca 100644 --- a/cmd/view-menu/main.go +++ b/cmd/view-menu/main.go @@ -8,11 +8,14 @@ import ( "github.com/hajimehoshi/ebiten" "code.ur.gs/lupine/ordoor/internal/assetstore" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/ui" ) var ( - gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") + configFile = flag.String("config", "config.toml", "Config file") + engine = flag.String("engine", "", "Override engine to use") + menuName = flag.String("menu", "", "Name of a menu, e.g. Main") winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension") @@ -28,12 +31,17 @@ type dlg struct { func main() { flag.Parse() - if *gamePath == "" || *menuName == "" { + if *configFile == "" || *menuName == "" { flag.Usage() os.Exit(1) } - assets, err := assetstore.New(*gamePath) + cfg, err := config.Load(*configFile, *engine) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + assets, err := assetstore.New(cfg.DefaultEngine()) if err != nil { log.Fatal(err) } diff --git a/cmd/view-obj/main.go b/cmd/view-obj/main.go index e3260dc..018c6fd 100644 --- a/cmd/view-obj/main.go +++ b/cmd/view-obj/main.go @@ -10,14 +10,17 @@ import ( "github.com/hajimehoshi/ebiten" "code.ur.gs/lupine/ordoor/internal/assetstore" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/ui" ) var ( - gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") - objFile = flag.String("obj-file", "", "Path of an .obj file, e.g. ./orig/Obj/TZEENTCH.OBJ") - objName = flag.String("obj-name", "", "Name of an .obj file, e.g. TZEENTCH") - sprIdx = flag.Int("spr-idx", 0, "Sprite index to start at") + configFile = flag.String("config", "config.toml", "Config file") + engine = flag.String("engine", "", "Override engine to use") + + objFile = flag.String("obj-file", "", "Path of an .obj file, e.g. ./orig/Obj/TZEENTCH.OBJ") + objName = flag.String("obj-name", "", "Name of an .obj file, e.g. TZEENTCH") + sprIdx = flag.Int("spr-idx", 0, "Sprite 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") @@ -42,12 +45,17 @@ type state struct { func main() { flag.Parse() - if *gamePath == "" || (*objName == "" && *objFile == "") { + if *configFile == "" || (*objName == "" && *objFile == "") { flag.Usage() os.Exit(1) } - assets, err := assetstore.New(*gamePath) + cfg, err := config.Load(*configFile, *engine) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + assets, err := assetstore.New(cfg.DefaultEngine()) if err != nil { log.Fatal("Failed to set up asset store: %v", err) } diff --git a/cmd/view-set/main.go b/cmd/view-set/main.go index 784623c..a718289 100644 --- a/cmd/view-set/main.go +++ b/cmd/view-set/main.go @@ -10,12 +10,15 @@ import ( "github.com/hajimehoshi/ebiten" "code.ur.gs/lupine/ordoor/internal/assetstore" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/ui" ) var ( - gamePath = flag.String("game-path", "./orig", "Path to a WH40K: Chaos Gate installation") - setName = flag.String("set", "", "Name of a set, e.g., map01") + configFile = flag.String("config", "config.toml", "Config file") + engine = flag.String("engine", "", "Override engine to use") + + setName = flag.String("set", "", "Name of a set, e.g., map01") winX = flag.Int("win-x", 1280, "Pre-scaled window X dimension") winY = flag.Int("win-y", 1024, "Pre-scaled window Y dimension") @@ -39,12 +42,17 @@ type state struct { func main() { flag.Parse() - if *gamePath == "" || *setName == "" { + if *configFile == "" || *setName == "" { flag.Usage() os.Exit(1) } - assets, err := assetstore.New(*gamePath) + cfg, err := config.Load(*configFile, *engine) + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + assets, err := assetstore.New(cfg.DefaultEngine()) if err != nil { log.Fatal(err) } diff --git a/config.toml.example b/config.toml.example index 154d4ec..1c7ecac 100644 --- a/config.toml.example +++ b/config.toml.example @@ -1,6 +1,21 @@ -[ordoor] - data_dir = "./orig" - video_player = ["mpv", "--no-config", "--keep-open=no", "--force-window=no", "--no-border", "--no-osc", "--fullscreen", "--no-input-default-bindings"] +video_player = ["mpv", "--no-config", "--keep-open=no", "--force-window=no", "--no-border", "--no-osc", "--fullscreen", "--no-input-default-bindings"] + +default_engine = "ordoor" + +[engines.geas] # Wages of War -> Gifts of Peace -> Geas + data_dir = "./WoW" + +[engines.ordoor] # Chaos Gate -> Order Door -> Ordoor + data_dir = "./CG" + palette = "ChaosGate" + +[engines.baps] # Soldiers At War -> Boys at Play -> Baps + data_dir = "./SaW" + palette = "SoldiersAtWar" + +# [engines.] Squad Leader -> ??? -> ??? +# data_dir = "./SL" +# palette = "SquadLeader" # may not be relevant? [options] play_movies = true diff --git a/internal/assetstore/assetstore.go b/internal/assetstore/assetstore.go index 227d625..90cd6d7 100644 --- a/internal/assetstore/assetstore.go +++ b/internal/assetstore/assetstore.go @@ -7,19 +7,16 @@ import ( "path/filepath" "strings" + "code.ur.gs/lupine/ordoor/internal/config" "code.ur.gs/lupine/ordoor/internal/data" "code.ur.gs/lupine/ordoor/internal/idx" ) -const ( - RootDir = "" // Used in the entryMap for entries pertaining to the root dir -) - type entryMap map[string]map[string]string // type AssetStore is responsible for lazily loading game data when it is // required. Applications shouldn't need to do anything except set one of these -// up, pointing at the game dir root, to access all assets. +// up, pointing at the game dir root, to access all assets for that game. // // Assets should be loaded on-demand to keep memory costs as low as possible. // Cross-platform differences such as filename case sensitivity are also dealt @@ -27,8 +24,12 @@ type entryMap map[string]map[string]string // // We assume the directory is read-only. You can run Refresh() if you make a // change. +// +// To mix assets from different games, either construct a synthetic directory +// or instantiate two separate asset stores. type AssetStore struct { RootDir string + // Palette // Case-insensitive file lookup. // {"":{"anim":"Anim", "obj":"Obj", ...}, "anim":{ "warhammer.ani":"WarHammer.ani" }, ...} @@ -50,9 +51,13 @@ type AssetStore struct { } // New returns a new AssetStore -func New(dir string) (*AssetStore, error) { +func New(engine *config.Engine) (*AssetStore, error) { + if engine == nil { + return nil, fmt.Errorf("Unconfigured engine passed to assetstore") + } + store := &AssetStore{ - RootDir: dir, + RootDir: engine.DataDir, } // fill entryMap @@ -70,7 +75,7 @@ func (a *AssetStore) Refresh() error { } newEntryMap := make(entryMap, len(rootEntries)) - newEntryMap[RootDir] = rootEntries + newEntryMap[""] = rootEntries for lower, natural := range rootEntries { path := filepath.Join(a.RootDir, natural) @@ -118,7 +123,7 @@ func (a *AssetStore) lookup(name, ext string, dirs ...string) (string, error) { dir = canonical(dir) if base, ok := a.entries[dir]; ok { if file, ok := base[filename]; ok { - actualDir := a.entries[RootDir][dir] + actualDir := a.entries[""][dir] return filepath.Join(a.RootDir, actualDir, file), nil } } diff --git a/internal/config/config.go b/internal/config/config.go index 2acf187..e47bf73 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,15 +2,16 @@ package config import ( "errors" + "fmt" "os" "path/filepath" "github.com/BurntSushi/toml" ) -type Ordoor struct { - DataDir string `toml:"data_dir"` - VideoPlayer []string `toml:"video_player"` +type Engine struct { + DataDir string `toml:"data_dir"` + Palette string `toml:"palette"` } // Things set in the options hash @@ -38,12 +39,40 @@ type Options struct { type Config struct { filename string `toml:"-"` + VideoPlayer []string `toml:"video_player"` + Engines map[string]Engine `toml:"engines"` + + DefaultEngineName string `toml:"default_engine"` + + // FIXME: options may well end up being per-engine too Defaults *Options `toml:"-"` - Ordoor `toml:"ordoor"` Options `toml:"options"` } -func Load(filename string) (*Config, error) { +func (c *Config) Engine(name string) *Engine { + engine, ok := c.Engines[name] + if !ok { + return nil + } + + return &engine +} + +func (c *Config) DefaultEngine() *Engine { + return c.Engine(c.DefaultEngineName) +} + +// TODO: case-insensitive lookup +func (c *Config) DataFile(engine string, path string) string { + cfg, ok := c.Engines[engine] + if !ok { + return "" + } + + return filepath.Join(cfg.DataDir, path) +} + +func Load(filename string, overrideDefaultEngine string) (*Config, error) { var out Config _, err := toml.DecodeFile(filename, &out) @@ -53,7 +82,15 @@ func Load(filename string) (*Config, error) { out.filename = filename - return &out, err + if overrideDefaultEngine != "" { + out.DefaultEngineName = overrideDefaultEngine + } + + if out.DefaultEngine() == nil { + return nil, fmt.Errorf("Default engine %q not configured", out.DefaultEngineName) + } + + return &out, nil } func (c *Config) HasUnsetOptions() bool { @@ -72,11 +109,6 @@ func (c *Config) Save() error { return toml.NewEncoder(f).Encode(c) } -// TODO: case-insensitive lookup -func (c *Config) DataFile(path string) string { - return filepath.Join(c.DataDir, path) -} - func (c *Config) ResetDefaults() error { if c.Defaults == nil { return errors.New("Defaults not available") diff --git a/internal/ordoor/ordoor.go b/internal/ordoor/ordoor.go index 2f5603d..386fddc 100644 --- a/internal/ordoor/ordoor.go +++ b/internal/ordoor/ordoor.go @@ -34,12 +34,12 @@ type Ordoor struct { } func Run(configFile string, overrideX, overrideY int) error { - cfg, err := config.Load(configFile) + cfg, err := config.Load(configFile, "ordoor") if err != nil { return fmt.Errorf("Couldn't load config file: %v", err) } - assets, err := assetstore.New(cfg.Ordoor.DataDir) + assets, err := assetstore.New(cfg.DefaultEngine()) if err != nil { return fmt.Errorf("Failed to initialize asset store: %v", err) } diff --git a/internal/ordoor/videos.go b/internal/ordoor/videos.go index 4b12329..f0ec017 100644 --- a/internal/ordoor/videos.go +++ b/internal/ordoor/videos.go @@ -6,7 +6,7 @@ import ( ) func (o *Ordoor) PlayVideo(name string, skippable bool) { - filename := o.config.DataFile("SMK/" + name + ".smk") + filename := o.config.DataFile("ordoor", "SMK/"+name+".smk") if len(o.config.VideoPlayer) == 0 { log.Printf("Video player not configured, skipping video %v", filename)