Start displaying characters on maps
This commit is contained in:
@@ -631,26 +631,45 @@ suggested above:
|
||||
|
||||
| Offset | Size | Meaning |
|
||||
| ------ | ---- | ------- |
|
||||
| 0 | 179 | ??? |
|
||||
| 0 | 178 | ??? |
|
||||
| 178 | 1 | Character type |
|
||||
| 179 | 80 | Character name |
|
||||
| 259 | 10 | Character attributes |
|
||||
| 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 | 927 | ??? |
|
||||
|
||||
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. Looking
|
||||
at the bytes for one character record, I can easily correlate certain bytes to
|
||||
various attributes; that's just done in code for the moment.
|
||||
just contains `\x02` and is then null-terminated all the way through - but maybe
|
||||
that's just a data thing.
|
||||
|
||||
Given two characters of the same time, just in different locations, differing
|
||||
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 - captains mess up a direct
|
||||
correlation - but a fixed offset table allows me to draw the characters \o/.
|
||||
Orientation is not yet deciphered, however.
|
||||
|
||||
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,
|
||||
@@ -658,10 +677,7 @@ 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}`.
|
||||
|
||||
Characters don't seem to take up multiple x,y squares, but they *can* take up
|
||||
multiple Z levels. So maybe this is a bounding box of some kind?
|
||||
`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
|
||||
@@ -673,21 +689,6 @@ 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.
|
||||
|
||||
How about their types? We need to be able to work out which animations to show,
|
||||
so they must be uniquely identified somehow. Ultramarine captain is animation
|
||||
group 12, while chaos lord is group... 14? Not certain. I don't see either of
|
||||
those numbers in a promising spot, anyway.
|
||||
|
||||
|
||||
I do see differences at around `0x170`:
|
||||
|
||||
```
|
||||
Ultramarine Captain: 00 00 00 00 00 00 00 00 50 03 00 00 00 00 00 00
|
||||
Chaos Lord: 00 00 00 00 11 01 00 00 47 03 00 04 00 00 02 00
|
||||
```
|
||||
|
||||
Maybe they're somewhat relevant, it's hard to say.
|
||||
|
||||
Thingies next: these aren't decoded at all yet, and the sizes seem to be
|
||||
variable.
|
||||
|
||||
|
@@ -1,6 +1,9 @@
|
||||
package assetstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/data"
|
||||
"code.ur.gs/lupine/ordoor/internal/idx"
|
||||
)
|
||||
|
||||
@@ -76,3 +79,50 @@ func (a *AssetStore) Animation(groupIdx, recIdx int) (*Animation, error) {
|
||||
|
||||
return &Animation{Frames: sprites}, nil
|
||||
}
|
||||
|
||||
func (a *AssetStore) CharacterAnimation(ctype data.CharacterType, action data.AnimAction) (*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)
|
||||
}
|
||||
|
||||
rec := ha.Index(ctype, action)
|
||||
|
||||
return a.Animation(group, rec)
|
||||
}
|
||||
|
@@ -45,6 +45,7 @@ type AssetStore struct {
|
||||
cursors map[CursorName]*Cursor
|
||||
fonts map[string]*Font
|
||||
generic *data.Generic
|
||||
hasAction *data.HasAction
|
||||
idx *idx.Idx
|
||||
images map[string]*ebiten.Image
|
||||
maps map[string]*Map
|
||||
@@ -111,6 +112,8 @@ func (a *AssetStore) Refresh() error {
|
||||
a.cursors = make(map[CursorName]*Cursor)
|
||||
a.entries = newEntryMap
|
||||
a.fonts = make(map[string]*Font)
|
||||
a.generic = nil
|
||||
a.hasAction = nil
|
||||
a.idx = nil
|
||||
a.images = make(map[string]*ebiten.Image)
|
||||
a.maps = make(map[string]*Map)
|
||||
|
@@ -66,6 +66,26 @@ func (a *AssetStore) DefaultOptions() (*config.Options, error) {
|
||||
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 {
|
||||
return i > 0
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"image"
|
||||
"log"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/data"
|
||||
"code.ur.gs/lupine/ordoor/internal/maps"
|
||||
)
|
||||
|
||||
@@ -102,19 +103,15 @@ func (m *Map) SpritesForCell(x, y, z int) ([]*Sprite, error) {
|
||||
sprites = append(sprites, sprite)
|
||||
}
|
||||
|
||||
// FIXME: this just marks character positions with sprite 19 for now.
|
||||
specialsObj, err := m.assets.Object("specials")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
chrSpr, err := specialsObj.Sprite(19)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, chr := range m.raw.Characters {
|
||||
if chr.XPos == x && chr.YPos == y && z == 1 { // FIXME: sort out ZPos
|
||||
sprites = append(sprites, chrSpr)
|
||||
// Look up the correct animation, get the frame, boom
|
||||
anim, err := m.assets.CharacterAnimation(chr.Type, data.AnimActionNone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sprites = append(sprites, anim.Frames[0])
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -94,6 +94,42 @@ type HasAction struct {
|
||||
bits bitfield.BitField
|
||||
}
|
||||
|
||||
var (
|
||||
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 (c CharacterType) String() string {
|
||||
if str, ok := cTypes[c]; ok {
|
||||
return str
|
||||
}
|
||||
|
||||
return "Unknown Character"
|
||||
}
|
||||
|
||||
func LoadHasAction(filename string) (*HasAction, error) {
|
||||
scanner, err := asciiscan.New(filename)
|
||||
if err != nil {
|
||||
@@ -161,6 +197,17 @@ func (h *HasAction) Actions(c CharacterType) []AnimAction {
|
||||
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() {
|
||||
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++ {
|
||||
|
@@ -12,6 +12,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/lunixbochs/struc"
|
||||
|
||||
"code.ur.gs/lupine/ordoor/internal/data"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -99,8 +101,9 @@ type Cell struct {
|
||||
}
|
||||
|
||||
type Character struct {
|
||||
Unknown1 []byte `struc:"[179]byte"`
|
||||
Name string `struc:"[80]byte"`
|
||||
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"`
|
||||
@@ -339,9 +342,11 @@ func nullTerminate(s *string) {
|
||||
|
||||
func (c *Character) String() string {
|
||||
return fmt.Sprintf(
|
||||
"squad=%v pos=(%v,%v) name=%q\n\t%3d %3d %3d %3d %3d\n\t%3d %3d ??? ??? %3d\n",
|
||||
"squad=%v pos=(%v,%v) type=%q name=%q\n"+
|
||||
"\t%3d %3d %3d %3d %3d\n\t%3d %3d ??? ??? %3d\n",
|
||||
c.SquadNumber,
|
||||
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,
|
||||
|
@@ -51,13 +51,13 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
|
||||
sw, sh := screen.Size()
|
||||
|
||||
topLeft := CartPt{
|
||||
X: float64(s.Viewpoint.X) - (2*cellWidth/s.Zoom), // Ensure all visible cells are rendered
|
||||
Y: float64(s.Viewpoint.Y) - (2*cellHeight/s.Zoom),
|
||||
X: float64(s.Viewpoint.X) - (2 * cellWidth / s.Zoom), // Ensure all visible cells are rendered
|
||||
Y: float64(s.Viewpoint.Y) - (2 * cellHeight / s.Zoom),
|
||||
}.ToISO()
|
||||
|
||||
bottomRight := CartPt{
|
||||
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
|
||||
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
|
||||
}.ToISO()
|
||||
|
||||
// X+Y is constant for all tiles in a column
|
||||
@@ -121,7 +121,7 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
|
||||
if err := screen.DrawImage(spr.Image, &op); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
x1, y1 := geo.Apply(0, 0)
|
||||
ebitenutil.DebugPrintAt(
|
||||
screen,
|
||||
@@ -133,12 +133,12 @@ func (s *Scenario) Draw(screen *ebiten.Image) error {
|
||||
ebitenutil.DebugPrintAt(screen, fmt.Sprintf("Sprites: %v", counter), 0, 16)
|
||||
|
||||
/*
|
||||
// debug: draw a square around the selected cell
|
||||
x2, y2 := geo.Apply(cellWidth, cellHeight)
|
||||
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, x2, y1, x2, y2, colornames.Green) // right line
|
||||
ebitenutil.DrawLine(screen, x1, y2, x2, y2, colornames.Green) // bottom line
|
||||
// debug: draw a square around the selected cell
|
||||
x2, y2 := geo.Apply(cellWidth, cellHeight)
|
||||
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, x2, y1, x2, y2, colornames.Green) // right line
|
||||
ebitenutil.DrawLine(screen, x1, y2, x2, y2, colornames.Green) // bottom line
|
||||
*/
|
||||
|
||||
return nil
|
||||
|
Reference in New Issue
Block a user