diff --git a/internal/assetstore/map.go b/internal/assetstore/map.go index 6c97863..32448f3 100644 --- a/internal/assetstore/map.go +++ b/internal/assetstore/map.go @@ -119,7 +119,7 @@ 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 == 1 { // FIXME: sort out ZPos + if chr.XPos == x && chr.YPos == y && z == 0 { // FIXME: sort out ZPos return chr } } diff --git a/internal/flow/flow.go b/internal/flow/flow.go index bd90b44..fdf499a 100644 --- a/internal/flow/flow.go +++ b/internal/flow/flow.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/inpututil" "code.ur.gs/lupine/ordoor/internal/assetstore" "code.ur.gs/lupine/ordoor/internal/config" @@ -118,6 +119,14 @@ func (f *Flow) Update(screenX, screenY int) error { if ebiten.IsKeyPressed(ebiten.KeyDown) { 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 { diff --git a/internal/flow/main_game.go b/internal/flow/main_game.go index 13ecb5f..73af299 100644 --- a/internal/flow/main_game.go +++ b/internal/flow/main_game.go @@ -1,5 +1,9 @@ package flow +import ( + "code.ur.gs/lupine/ordoor/internal/maps" +) + // TODO: There are Chaos and Ultramarine versions of MainGame. Do we really want // to duplicate everything for both? @@ -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 +} diff --git a/internal/maps/maps.go b/internal/maps/maps.go index 94ed490..cbb5891 100644 --- a/internal/maps/maps.go +++ b/internal/maps/maps.go @@ -319,7 +319,8 @@ func loadMapFile(filename string) (*GameMap, error) { nullTerminate(&out.Title) nullTerminate(&out.Briefing) - for i, chr := range out.Characters { + for i, _ := range out.Characters { + chr := &out.Characters[i] nullTerminate(&chr.Name) fmt.Printf("Character %v: %s\n", i, chr.String()) } diff --git a/internal/menus/menus.go b/internal/menus/menus.go index 280cc75..48609e1 100644 --- a/internal/menus/menus.go +++ b/internal/menus/menus.go @@ -33,7 +33,7 @@ const ( SubTypeLineBriefing SubMenuType = 41 SubTypeThumb SubMenuType = 45 // A "thumb" appears to be a vertical slider SubTypeInvokeButton SubMenuType = 50 - SubTypeDoorHotspot3 SubMenuType = 60 // Maybe? Appears in Arrange.mnu + SubTypeClickText SubMenuType = 60 SubTypeOverlay SubMenuType = 61 SubTypeHypertext SubMenuType = 70 SubTypeCheckbox SubMenuType = 91 diff --git a/internal/scenario/draw.go b/internal/scenario/draw.go index 358556a..6440167 100644 --- a/internal/scenario/draw.go +++ b/internal/scenario/draw.go @@ -36,7 +36,7 @@ func (s *Scenario) Update(screenX, screenY int) error { } // FIXME: adjust for Z level - s.selectedCell = screenPos.ToISO() + s.highlightedCell = screenPos.ToISO() return nil } @@ -112,7 +112,7 @@ func (s *Scenario) Draw(screen *ebiten.Image) error { } 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.Translate(-209, -332) op.GeoM.Translate(float64(spr.Rect.Min.X), float64(spr.Rect.Min.Y)) @@ -125,7 +125,7 @@ func (s *Scenario) Draw(screen *ebiten.Image) error { x1, y1 := geo.Apply(0, 0) ebitenutil.DebugPrintAt( 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(y1), ) diff --git a/internal/scenario/manage.go b/internal/scenario/manage.go index 168e6e1..eab60a6 100644 --- a/internal/scenario/manage.go +++ b/internal/scenario/manage.go @@ -1,6 +1,8 @@ package scenario import ( + "log" + "code.ur.gs/lupine/ordoor/internal/maps" ) @@ -10,8 +12,23 @@ type CellPoint struct { } func (s *Scenario) CellAtCursor() (*maps.Cell, CellPoint) { - cell := s.area.Cell(int(s.selectedCell.X), int(s.selectedCell.Y), 0) - return cell, CellPoint{IsoPt: s.selectedCell, Z: 0} + cell := s.area.Cell(int(s.highlightedCell.X), int(s.highlightedCell.Y), 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) { diff --git a/internal/scenario/scenario.go b/internal/scenario/scenario.go index 14d3bc9..eb00814 100644 --- a/internal/scenario/scenario.go +++ b/internal/scenario/scenario.go @@ -5,15 +5,18 @@ import ( "image" "code.ur.gs/lupine/ordoor/internal/assetstore" + "code.ur.gs/lupine/ordoor/internal/maps" ) type Scenario struct { area *assetstore.Map specials *assetstore.Object - tick int - turn int - selectedCell IsoPt + tick int + turn int + + highlightedCell IsoPt + selectedCharacter *maps.Character // All these must be modified by user actions somehow. // TODO: extract into the idea of a viewport passed to Update / Draw somehow? diff --git a/internal/ui/group.go b/internal/ui/group.go index 96d9591..1f2de1b 100644 --- a/internal/ui/group.go +++ b/internal/ui/group.go @@ -78,8 +78,10 @@ func (d *Driver) buildRecord(r *menus.Record) (*Widget, error) { switch r.Type { case menus.SubTypeSimpleButton, menus.SubTypeInvokeButton: _, widget, err = d.buildButton(r.Props()) - case menus.SubTypeDoorHotspot1, menus.SubTypeDoorHotspot2, menus.SubTypeDoorHotspot3: + case menus.SubTypeDoorHotspot1, menus.SubTypeDoorHotspot2: _, widget, err = d.buildDoorHotspot(r.Props()) + case menus.SubTypeClickText: + _, widget, err = d.buildClickText(r.Props()) case menus.SubTypeOverlay: _, widget, err = d.buildOverlay(r.Props()) case menus.SubTypeHypertext: diff --git a/internal/ui/list_box.go b/internal/ui/list_box.go index 42a4ba4..dbc633e 100644 --- a/internal/ui/list_box.go +++ b/internal/ui/list_box.go @@ -157,9 +157,9 @@ func (l *listBox) refresh() { // FIXME: noninteractive isn't set up for dynamic text yet. Need to // generate textImg on demand instead of once at start. if ni.label != nil { - ni.label.text = "" + ni.label.str = "" if len(l.strings) > l.offset+i { - ni.label.text = l.strings[l.offset+i] + ni.label.str = l.strings[l.offset+i] } } } diff --git a/internal/ui/noninteractive.go b/internal/ui/noninteractive.go index c78316d..e370ff4 100644 --- a/internal/ui/noninteractive.go +++ b/internal/ui/noninteractive.go @@ -32,12 +32,13 @@ type noninteractive struct { hoverImpl } -// Paint some text to screen +// Paint some text to screen, possibly settable type label struct { - align AlignMode - rect image.Rectangle - text string - font *assetstore.Font + locator string + align AlignMode + rect image.Rectangle + font *assetstore.Font + valueImpl } // 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 } +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 func (d *Driver) buildOverlay(p *menus.Properties) (*noninteractive, *Widget, error) { 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) ni.label = &label{ - font: fnt, - rect: ni.rect, // We will be centered by default - text: p.Text, + font: fnt, + rect: ni.rect, // We will be centered by default + valueImpl: valueImpl{str: p.Text}, } } else { log.Printf("Overlay without text detected in %v", p.Locator) @@ -253,6 +281,10 @@ func (a *animationHover) setHoverState(value bool) { 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 // the centre of the rect. // @@ -260,7 +292,7 @@ func (a *animationHover) setHoverState(value bool) { func (l *label) pos() image.Point { pos := l.rect.Min - textRect := l.font.CalculateBounds(l.text) + textRect := l.font.CalculateBounds(l.str) // Centre the text horizontally if l.align == AlignModeCentre { @@ -287,7 +319,7 @@ func (l *label) regions(tick int) []region { pt := l.pos() - for _, r := range l.text { + for _, r := range l.str { glyph, err := l.font.Glyph(r) if err != nil { log.Printf("FIXME: ignoring misssing glyph %v", r)