Compare commits
18 Commits
soldiers-a
...
main
Author | SHA1 | Date | |
---|---|---|---|
55c2232e08 | |||
ac4675fa2c | |||
5f7654d267 | |||
89888ce004 | |||
16767da6f1 | |||
c5b80ed8bc | |||
891edecc60 | |||
85979834c8 | |||
96dbb297cd | |||
92fa0fc5d6 | |||
c5e6abb798 | |||
5df050b4ef | |||
4d336b9189 | |||
3b7cfb6ecc | |||
7677c30572 | |||
eac6017c2c | |||
f971ba320c | |||
cf624cc77b |
@@ -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/)
|
||||||
|
@@ -139,16 +139,15 @@ func loadMapsFrom(mapsPath string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Maps in %s:", mapsPath)
|
log.Printf("Maps in %s:", mapsPath)
|
||||||
for key, gameMap := range gameMaps {
|
for key, gm := range gameMaps {
|
||||||
rect := gameMap.Rect()
|
rect := gm.Rect()
|
||||||
hdr := gameMap.Header
|
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
" * `%s`: IsCampaignMap=%v W=%v:%v L=%v:%v SetName=%s\n",
|
" * `%s`: IsCampaignMap=%v W=%v:%v L=%v:%v SetName=%s\n",
|
||||||
key,
|
key,
|
||||||
hdr.IsCampaignMap,
|
gm.IsCampaignMap,
|
||||||
rect.Min.X, rect.Max.X,
|
rect.Min.X, rect.Max.X,
|
||||||
rect.Min.Y, rect.Max.Y,
|
rect.Min.Y, rect.Max.Y,
|
||||||
string(hdr.SetName[:]),
|
string(gm.SetName),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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()
|
||||||
}
|
}
|
||||||
|
@@ -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() {
|
||||||
|
@@ -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
|
||||||
|
@@ -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,16 +173,12 @@ 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++ {
|
||||||
cell := gameMap.Cells.At(x, y, int(e.state.zIdx))
|
cell := gameMap.At(x, y, int(e.state.zIdx))
|
||||||
imd.Set(x, y, makeColour(&cell, e.state.cellIdx))
|
imd.Set(x, y, makeColour(cell, e.state.cellIdx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
@@ -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() {
|
||||||
|
@@ -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() {
|
||||||
|
@@ -598,7 +598,8 @@ Ignoring them for now, here's a first guess at a header:
|
|||||||
| 24 | 2 | ??? - varies. Seems related to character/squad position? |
|
| 24 | 2 | ??? - varies. Seems related to character/squad position? |
|
||||||
| 26 | 2 | ??? - invariant `00 04` |
|
| 26 | 2 | ??? - invariant `00 04` |
|
||||||
| 28 | 4 | ??? - varies (0 vs 5) |
|
| 28 | 4 | ??? - varies (0 vs 5) |
|
||||||
| 32 | 4 | Number of thingies (26 vs 1) |
|
| 32 | 1 | Number of thingies |
|
||||||
|
| 33 | 3 | ???. With a Lord of Change on the map, only one byte works as thingies count |
|
||||||
| 36 | 20 | Padding? |
|
| 36 | 20 | Padding? |
|
||||||
|
|
||||||
56 bytes of data is interesting because the value of that first, ignored byte is
|
56 bytes of data is interesting because the value of that first, ignored byte is
|
||||||
@@ -609,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.
|
||||||
@@ -625,7 +621,84 @@ Generating a map with no characters at all, the trailer is 2,447 bytes, and the
|
|||||||
mission title starts at 0x3B (59). So we can say we have 20 bytes of padding as
|
mission title starts at 0x3B (59). So we can say we have 20 bytes of padding as
|
||||||
a first approximation?
|
a first approximation?
|
||||||
|
|
||||||
The "trailer trailer", for want of a better term, seems to be organised as:
|
Here's where we're at with the per-character data, going from the padding values
|
||||||
|
suggested above:
|
||||||
|
|
||||||
|
| Offset | Size | Meaning |
|
||||||
|
| ------ | ---- | ------- |
|
||||||
|
| 0 | 178 | ??? |
|
||||||
|
| 178 | 1 | Character type |
|
||||||
|
| 179 | 80 | Character name |
|
||||||
|
| 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 | ??? |
|
||||||
|
| 764 | 1(?) | Squad number |
|
||||||
|
| 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
|
||||||
|
character names from Chapter01 correctly, with the exception of record 57 which
|
||||||
|
just contains `\x02` and is then null-terminated all the way through - but maybe
|
||||||
|
that's just a data thing.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
* `0x103 - 0x10c` (hodgepodge)
|
||||||
|
* `0x178 - 0x1be` (hodgepodge)
|
||||||
|
* `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,
|
||||||
|
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,
|
||||||
|
though.
|
||||||
|
|
||||||
|
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
|
||||||
|
`0x1b{d,e}`. This may be some kind of multiple-squares-taken-up thing.
|
||||||
|
|
||||||
|
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
|
||||||
|
immediately after this offset changed into a large number of coordinate-like
|
||||||
|
sets of values - far too many for it to actually be a bounding box. However, the
|
||||||
|
first one remains good as a position specifier.
|
||||||
|
|
||||||
|
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
|
||||||
|
first-indexed value.
|
||||||
|
|
||||||
|
Thingies next: these aren't decoded at all yet, and the sizes seem to be
|
||||||
|
variable.
|
||||||
|
|
||||||
|
| Offset | Size | Meaning |
|
||||||
|
| ------ | ---- | ------- |
|
||||||
|
| | | |
|
||||||
|
|
||||||
|
|
||||||
|
Finally, the "trailer trailer", for want of a better term, seems to be organised
|
||||||
|
as:
|
||||||
|
|
||||||
| Offset | Size | Meaning |
|
| Offset | Size | Meaning |
|
||||||
| ----- | ---- | ------- |
|
| ----- | ---- | ------- |
|
||||||
@@ -633,6 +706,8 @@ The "trailer trailer", for want of a better term, seems to be organised as:
|
|||||||
| 255 | 2048 | Briefing |
|
| 255 | 2048 | Briefing |
|
||||||
| 2304 | 85 | ??? - each byte is 1 or 0. Spaced so it may be partly uint32 |
|
| 2304 | 85 | ??? - each byte is 1 or 0. Spaced so it may be partly uint32 |
|
||||||
|
|
||||||
|
This duplicates the information found in the `.TXT` files. No idea what the end
|
||||||
|
data is yet.
|
||||||
|
|
||||||
## Soldiers At War
|
## Soldiers At War
|
||||||
|
|
||||||
|
36
go.mod
36
go.mod
@@ -1,19 +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-20200707160740-784aaebc1d40
|
||||||
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/samuel/go-pcx v0.0.0-20210515040514-6a5ce4d132f7
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 // indirect
|
golang.org/x/image v0.21.0
|
||||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/mobile v0.0.0-20200329125638-4c31acba0007 // indirect
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
|
)
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
|
||||||
|
require (
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
135
go.sum
135
go.sum
@@ -1,111 +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-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||||
|
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/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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
|
@@ -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)
|
||||||
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ func (a *AssetStore) Map(name string) (*Map, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := &Map{
|
m := &Map{
|
||||||
Rect: raw.Rect(),
|
Rect: raw.Rect(),
|
||||||
assets: a,
|
assets: a,
|
||||||
raw: raw,
|
raw: raw,
|
||||||
set: set,
|
set: set,
|
||||||
@@ -74,8 +75,8 @@ func (m *Map) LoadSprites() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: get rid of this
|
// FIXME: get rid of this
|
||||||
func (m *Map) Cell(x, y, z int) maps.Cell {
|
func (m *Map) Cell(x, y, z int) *maps.Cell {
|
||||||
return m.raw.Cells.At(x, y, z)
|
return m.raw.At(x, y, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpritesForCell returns the sprites needed to correctly render this cell.
|
// SpritesForCell returns the sprites needed to correctly render this cell.
|
||||||
@@ -101,6 +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 {
|
||||||
|
// Look up the correct animation, get the frame, boom shakalaka
|
||||||
|
anim, err := m.assets.CharacterAnimation(chr.Type, data.AnimActionNone, int(chr.Orientation))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
@@ -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"
|
||||||
)
|
)
|
||||||
|
@@ -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),
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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++ {
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
@@ -6,12 +6,14 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/lunixbochs/struc"
|
||||||
|
|
||||||
|
"code.ur.gs/lupine/ordoor/internal/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -31,93 +33,131 @@ const (
|
|||||||
cellCount = MaxHeight * MaxLength * MaxWidth
|
cellCount = MaxHeight * MaxLength * MaxWidth
|
||||||
)
|
)
|
||||||
|
|
||||||
type Header struct {
|
type GameMap struct {
|
||||||
IsCampaignMap uint32 // Tentatively: 0 = no, 1 = yes
|
// Main Header
|
||||||
MinWidth uint32
|
IsCampaignMap bool `struc:"uint32"` // Tentatively: 0 = no, 1 = yes
|
||||||
MinLength uint32
|
MinWidth int `struc:"uint32"`
|
||||||
MaxWidth uint32
|
MinLength int `struc:"uint32"`
|
||||||
MaxLength uint32
|
MaxWidth int `struc:"uint32"`
|
||||||
Unknown1 uint32
|
MaxLength int `struc:"uint32"`
|
||||||
Unknown2 uint32
|
Unknown1 int `struc:"uint32"`
|
||||||
Unknown3 uint32
|
Unknown2 int `struc:"uint32"`
|
||||||
Unknown4 uint32
|
Unknown3 int `struc:"uint32"`
|
||||||
Magic [8]byte // "\x08\x00WHMAP\x00"
|
Unknown4 int `struc:"uint32"`
|
||||||
Unknown5 uint32
|
Magic []byte `struc:"[8]byte"` // "\x08\x00WHMAP\x00"
|
||||||
Unknown6 uint32
|
Unknown5 int `struc:"uint32"`
|
||||||
SetName [8]byte // Links to a filename in `/Sets/*.set`
|
Unknown6 int `struc:"uint32"`
|
||||||
// Need to investigate the rest of the header too
|
SetName string `struc:"[8]byte"` // Links to a filename in `/Sets/*.set`
|
||||||
|
Padding []byte `struc:"[212]byte"`
|
||||||
|
|
||||||
|
// Per-cell data
|
||||||
|
NumCells int `struc:"skip"` // FIXME: We can't use []Cell below without this field
|
||||||
|
Cells []Cell `struc:"[]Cell,sizefrom=NumCells"`
|
||||||
|
|
||||||
|
// Trailer header
|
||||||
|
Discard1 [3]byte `struc:"[3]byte"` // First byte is size of trailer header?
|
||||||
|
|
||||||
|
TrailerMaxWidth int `struc:"uint32"`
|
||||||
|
TrailerMaxLength int `struc:"uint32"`
|
||||||
|
TrailerMinWidth int `struc:"uint32"`
|
||||||
|
TrailerMinLength int `struc:"uint32"`
|
||||||
|
|
||||||
|
NumCharacters int `struc:"uint32"`
|
||||||
|
|
||||||
|
TrailerUnknown1 int `struc:"uint32"`
|
||||||
|
TrailerUnknown2 int `struc:"uint16"`
|
||||||
|
TrailerUnknown3 int `struc:"uint16"`
|
||||||
|
TrailerUnknown4 int `struc:"uint32"`
|
||||||
|
|
||||||
|
NumThingies int `struc:"byte"`
|
||||||
|
TrailerUnknown5 []byte `struc:"[3]byte"`
|
||||||
|
Padding1 []byte `struc:"[20]byte"`
|
||||||
|
|
||||||
|
// FIXME: The rest is trash until Character & Thingy are worked out
|
||||||
|
|
||||||
|
Characters []Character `struc:"[]Character,sizefrom=NumCharacters"`
|
||||||
|
Thingies []Thingy `struc:"[]Thingy,sizefrom=NumThingies"`
|
||||||
|
|
||||||
|
Title string `struc:"[255]byte"`
|
||||||
|
Briefing string `struc:"[2048]byte"`
|
||||||
|
|
||||||
|
// Maybe? each contains either 0 or 1? Hard to say
|
||||||
|
TrailerUnknown6 []byte `struc:"[85]byte"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrailerHeader struct {
|
type Cell struct {
|
||||||
Discard1 [3]byte // No idea what this lot is
|
DoorAndCanisterRelated byte `struc:"byte"`
|
||||||
MaxWidth uint32
|
DoorLockAndReactorRelated byte `struc:"byte"`
|
||||||
MaxLength uint32
|
Unknown2 byte `struc:"byte"`
|
||||||
MinWidth uint32
|
Surface ObjRef
|
||||||
MinLength uint32
|
Left ObjRef
|
||||||
|
Right ObjRef
|
||||||
NumCharacters uint32
|
Center ObjRef
|
||||||
|
Unknown11 byte `struc:"byte"`
|
||||||
Unknown1 uint32
|
Unknown12 byte `struc:"byte"`
|
||||||
Unknown2 uint16
|
Unknown13 byte `struc:"byte"`
|
||||||
Unknown3 uint16
|
Unknown14 byte `struc:"byte"`
|
||||||
Unknown4 uint32
|
SquadRelated byte `struc:"byte"`
|
||||||
NumThingies uint32
|
|
||||||
Padding1 [20]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type TrailerTrailer struct {
|
|
||||||
Title [255]byte
|
|
||||||
Briefing [2048]byte
|
|
||||||
Unknown1 [85]uint8 // Maybe? each contains either 0 or 1? Hard to say
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Character struct {
|
type Character struct {
|
||||||
Unknown1 uint32
|
Unknown1 []byte `struc:"[178]byte"`
|
||||||
|
Type data.CharacterType `struc:"byte"`
|
||||||
|
Name string `struc:"[80]byte"`
|
||||||
|
|
||||||
|
// Attributes guessed by matching up numbers. Starts at 0x103
|
||||||
|
WeaponSkill int `struc:"byte"`
|
||||||
|
BallisticSkill int `struc:"byte"`
|
||||||
|
Unknown2 byte `struc:"byte"`
|
||||||
|
Leadership int `struc:"byte"`
|
||||||
|
Toughness int `struc:"byte"`
|
||||||
|
Strength int `struc:"byte"`
|
||||||
|
ActionPoints int `struc:"byte"`
|
||||||
|
Unknown3 byte `struc:"byte"`
|
||||||
|
Unknown4 byte `struc:"byte"`
|
||||||
|
Health int `struc:"byte"`
|
||||||
|
|
||||||
|
Unknown5 []byte `struc:"[91]byte"`
|
||||||
|
Armor int `struc:"byte"`
|
||||||
|
Unknown6 []byte `struc:"[84]byte"`
|
||||||
|
YPos int `struc:"byte"` // These are actually much more complicated
|
||||||
|
XPos int `struc:"byte"`
|
||||||
|
Unknown7 []byte `struc:"[317]byte"`
|
||||||
|
SquadNumber byte `struc:"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
|
||||||
}
|
}
|
||||||
|
|
||||||
type Characters []Character
|
type Characters []Character
|
||||||
|
|
||||||
// TODO. These are triggers/reactors/etc.
|
// TODO. These are triggers/reactors/etc.
|
||||||
type Thingy struct {}
|
type Thingy struct {
|
||||||
|
Unknown1 int `struc:"uint32"`
|
||||||
|
}
|
||||||
|
|
||||||
type Thingies []Thingy
|
type Thingies []Thingy
|
||||||
|
|
||||||
func (h Header) Width() int {
|
func (g *GameMap) MapSetName() string {
|
||||||
return int(h.MaxWidth - h.MinWidth)
|
return g.SetName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Header) Length() int {
|
func (g *GameMap) MapSetFilename() string {
|
||||||
return int(h.MaxLength - h.MinLength)
|
return g.MapSetName() + ".set"
|
||||||
}
|
|
||||||
|
|
||||||
func (h Header) Height() int {
|
|
||||||
return MaxHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Header) MapSetName() string {
|
|
||||||
idx := bytes.IndexByte(h.SetName[:], 0)
|
|
||||||
if idx < 0 {
|
|
||||||
idx = 8 // all 8 bytes are used
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(h.SetName[0:idx:idx])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h Header) MapSetFilename() string {
|
|
||||||
return h.MapSetName() + ".set"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ObjRef struct {
|
type ObjRef struct {
|
||||||
AreaByte byte
|
AreaByte byte `struc:"byte"`
|
||||||
SpriteAndFlagByte byte
|
SpriteAndFlagByte byte `struc:"byte"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o ObjRef) Sprite() int {
|
func (o *ObjRef) Sprite() int {
|
||||||
// The top bit seems to be a flag of some kind
|
// The top bit seems to be a flag of some kind
|
||||||
return int(o.SpriteAndFlagByte & 0x7f)
|
return int(o.SpriteAndFlagByte & 0x7f)
|
||||||
}
|
}
|
||||||
@@ -127,21 +167,6 @@ func (o ObjRef) IsActive() bool {
|
|||||||
return (o.SpriteAndFlagByte & 0x80) == 0x80
|
return (o.SpriteAndFlagByte & 0x80) == 0x80
|
||||||
}
|
}
|
||||||
|
|
||||||
type Cell struct {
|
|
||||||
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:
|
||||||
@@ -181,46 +206,26 @@ func (c *Cell) At(n int) byte {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cells is always a fixed size; use At to get a cell according to x,y,z
|
func (g *GameMap) At(x, y, z int) *Cell {
|
||||||
type Cells []Cell
|
return &g.Cells[(z*MaxLength*MaxWidth)+(y*MaxWidth)+x]
|
||||||
|
|
||||||
func (c Cells) At(x, y, z int) Cell {
|
|
||||||
return c[(z*MaxLength*MaxWidth)+(y*MaxWidth)+x]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Header) Check() []error {
|
func (g *GameMap) Check() error {
|
||||||
var out []error
|
if bytes.Compare(expectedMagic, g.Magic) != 0 {
|
||||||
if h.IsCampaignMap > 1 {
|
return fmt.Errorf("Unexpected magic value: %v", g.Magic)
|
||||||
out = append(out, fmt.Errorf("Expected 0 or 1 for IsCampaignMap, got %v", h.IsCampaignMap))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Compare(expectedMagic, h.Magic[:]) != 0 {
|
// TODO: other consistency checks
|
||||||
out = append(out, fmt.Errorf("Unexpected magic value: %v", h.Magic))
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
type GameMap struct {
|
|
||||||
Header
|
|
||||||
Cells
|
|
||||||
TrailerHeader
|
|
||||||
Characters
|
|
||||||
Thingies
|
|
||||||
TrailerTrailer
|
|
||||||
|
|
||||||
// TODO: parse this into sections. This is the text that comes from the
|
|
||||||
// .TXT file. Maybe we don't need it at all since it should be duplicated in
|
|
||||||
// the trailer.
|
|
||||||
Text string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GameMap) Rect() image.Rectangle {
|
func (m *GameMap) Rect() image.Rectangle {
|
||||||
return image.Rect(
|
return image.Rect(
|
||||||
int(m.Header.MinWidth),
|
int(m.MinWidth),
|
||||||
int(m.Header.MinLength),
|
int(m.MinLength),
|
||||||
int(m.Header.MaxWidth),
|
int(m.MaxWidth),
|
||||||
int(m.Header.MaxLength),
|
int(m.MaxLength),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,23 +248,17 @@ func LoadGameMap(prefix string) (*GameMap, error) {
|
|||||||
return nil, fmt.Errorf("Couldn't find %s.{map,txt}, even ignoring case", prefix)
|
return nil, fmt.Errorf("Couldn't find %s.{map,txt}, even ignoring case", prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A game map is composed of two files: .map and .txt
|
// A game map is composed of two files: .map and .txt. We ignore the text file,
|
||||||
|
// since the content is replicated in the map file.
|
||||||
func LoadGameMapByFiles(mapFile, txtFile string) (*GameMap, error) {
|
func LoadGameMapByFiles(mapFile, txtFile string) (*GameMap, error) {
|
||||||
out, err := loadMapFile(mapFile)
|
out, err := loadMapFile(mapFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: load text and parse into sections
|
if err := out.Check(); err != nil {
|
||||||
txt, err := ioutil.ReadFile(txtFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out.Text = string(txt)
|
|
||||||
|
|
||||||
for _, err := range out.Check() {
|
|
||||||
log.Printf("%s: %v", mapFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
@@ -297,6 +296,7 @@ func LoadGameMaps(dir string) (map[string]*GameMap, error) {
|
|||||||
|
|
||||||
func loadMapFile(filename string) (*GameMap, error) {
|
func loadMapFile(filename string) (*GameMap, error) {
|
||||||
var out GameMap
|
var out GameMap
|
||||||
|
out.NumCells = cellCount
|
||||||
|
|
||||||
mf, err := os.Open(filename)
|
mf, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -312,52 +312,46 @@ func loadMapFile(filename string) (*GameMap, error) {
|
|||||||
|
|
||||||
defer zr.Close()
|
defer zr.Close()
|
||||||
|
|
||||||
if err := binary.Read(zr, binary.LittleEndian, &out.Header); err != nil {
|
if err := struc.UnpackWithOrder(zr, &out, binary.LittleEndian); err != nil {
|
||||||
return nil, fmt.Errorf("Error parsing %s: %v", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// no gzip.SeekReader, so discard unread header bytes for now
|
|
||||||
discardSize := int64(cellDataOffset - binary.Size(&out.Header))
|
|
||||||
if _, err := io.CopyN(ioutil.Discard, zr, discardSize); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Cells = make(Cells, cellCount)
|
// Trim any trailing nulls off of the strings
|
||||||
if err := binary.Read(zr, binary.LittleEndian, &out.Cells); err != nil {
|
nullTerminate(&out.SetName)
|
||||||
return nil, fmt.Errorf("Error parsing cells for %s: %v", filename, err)
|
nullTerminate(&out.Title)
|
||||||
|
nullTerminate(&out.Briefing)
|
||||||
|
|
||||||
|
for i, _ := range out.Characters {
|
||||||
|
chr := &out.Characters[i]
|
||||||
|
nullTerminate(&chr.Name)
|
||||||
|
fmt.Printf("Character %v: %s\n", i, chr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := binary.Read(zr, binary.LittleEndian, &out.TrailerHeader); err != nil {
|
fmt.Printf("Mission Title: %q\n", out.Title)
|
||||||
return nil, fmt.Errorf("Error parsing trailer header for %s: %v", filename, err)
|
fmt.Printf("Mission Briefing: %q\n", out.Briefing)
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Trailer Header: %#+v", out.TrailerHeader)
|
|
||||||
/*
|
|
||||||
// TODO: until we know how large each character record should be, we can't read this lot
|
|
||||||
|
|
||||||
out.Characters = make(Characters, int(out.TrailerHeader.NumCharacters))
|
|
||||||
if err := binary.Read(zr, binary.LittleEndian, &out.Characters); err != nil {
|
|
||||||
return nil, fmt.Errorf("Error parsing characters for %s: %v", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
out.Thingies = make(Thingies, int(out.TrailerHeader.NumThingies))
|
|
||||||
if err := binary.Read(zr, binary.LittleEndian, &out.Thingies); err != nil {
|
|
||||||
return nil, fmt.Errorf("Error parsing thingies for %s: %v", filename, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := binary.Read(zr, binary.LittleEndian, &out.TrailerTrailer); err != nil {
|
|
||||||
return nil, fmt.Errorf("Error parsing trailer trailer for %s: %v", filename, err)
|
|
||||||
}
|
|
||||||
log.Printf("Trailer Trailer: %s", out.TrailerTrailer.String())
|
|
||||||
*/
|
|
||||||
return &out, nil
|
return &out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TrailerTrailer) String() string {
|
func nullTerminate(s *string) {
|
||||||
|
sCpy := *s
|
||||||
|
idx := strings.Index(sCpy, "\x00")
|
||||||
|
if idx < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = sCpy[0:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Character) String() string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"title=%q briefing=%q rest=%#+v",
|
"squad=%v pos=(%v,%v) type=%q name=%q\n"+
|
||||||
strings.TrimRight(string(t.Title[:]), "\x00"),
|
"\t%3d %3d %3d %3d %3d\n\t%3d %3d ??? ??? %3d\n",
|
||||||
strings.TrimRight(string(t.Briefing[:]), "\x00"),
|
c.SquadNumber,
|
||||||
t.Unknown1,
|
c.XPos, c.YPos,
|
||||||
|
c.Type.String(),
|
||||||
|
c.Name,
|
||||||
|
c.ActionPoints, c.Health, c.Armor, c.BallisticSkill, c.WeaponSkill,
|
||||||
|
c.Strength, c.Toughness /*c.Initiative, c.Attacks,*/, c.Leadership,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -9,9 +11,24 @@ type CellPoint struct {
|
|||||||
Z int
|
Z int
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@@ -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,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hajimehoshi/ebiten"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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:
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user